Skip to content

Commit b138bff

Browse files
authored
Implement packed-refs (#344)
1 parent 8ad71a4 commit b138bff

File tree

5 files changed

+389
-147
lines changed

5 files changed

+389
-147
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
using System;
3+
using System.IO;
4+
using System.Linq;
5+
using TestUtilities;
6+
using Xunit;
7+
8+
namespace Microsoft.Build.Tasks.Git.UnitTests
9+
{
10+
public class GitReferenceResolverTests
11+
{
12+
[Fact]
13+
public void ResolveReference()
14+
{
15+
using var temp = new TempRoot();
16+
17+
var gitDir = temp.CreateDirectory();
18+
19+
var commonDir = temp.CreateDirectory();
20+
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");
21+
22+
refsHeadsDir.CreateFile("master").WriteAllText("0000000000000000000000000000000000000000");
23+
refsHeadsDir.CreateFile("br1").WriteAllText("ref: refs/heads/br2");
24+
refsHeadsDir.CreateFile("br2").WriteAllText("ref: refs/heads/master");
25+
26+
var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path);
27+
28+
Assert.Equal("0123456789ABCDEFabcdef000000000000000000", resolver.ResolveReference("0123456789ABCDEFabcdef000000000000000000"));
29+
30+
Assert.Equal("0000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/master"));
31+
Assert.Equal("0000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br1"));
32+
Assert.Equal("0000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br2"));
33+
34+
// branch without commits (emtpy repository) will have not file in refs/heads:
35+
Assert.Null(resolver.ResolveReference("ref: refs/heads/none"));
36+
37+
Assert.Null(resolver.ResolveReference("ref: refs/heads/rec1 "));
38+
Assert.Null(resolver.ResolveReference("ref: refs/heads/none" + string.Join("/", Path.GetInvalidPathChars())));
39+
}
40+
41+
[Fact]
42+
public void ResolveReference_Errors()
43+
{
44+
using var temp = new TempRoot();
45+
46+
var gitDir = temp.CreateDirectory();
47+
48+
var commonDir = temp.CreateDirectory();
49+
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");
50+
51+
refsHeadsDir.CreateFile("rec1").WriteAllText("ref: refs/heads/rec2");
52+
refsHeadsDir.CreateFile("rec2").WriteAllText("ref: refs/heads/rec1");
53+
54+
var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path);
55+
56+
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref: refs/heads/rec1"));
57+
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref: xyz/heads/rec1"));
58+
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref:refs/heads/rec1"));
59+
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("refs/heads/rec1"));
60+
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 39)));
61+
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 41)));
62+
}
63+
64+
[Fact]
65+
public void ResolveReference_Packed()
66+
{
67+
using var temp = new TempRoot();
68+
69+
var gitDir = temp.CreateDirectory();
70+
71+
gitDir.CreateFile("packed-refs").WriteAllText(
72+
@"# pack-refs with: peeled fully-peeled sorted
73+
1111111111111111111111111111111111111111 refs/heads/master
74+
2222222222222222222222222222222222222222 refs/heads/br2
75+
");
76+
var commonDir = temp.CreateDirectory();
77+
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");
78+
79+
refsHeadsDir.CreateFile("br1").WriteAllText("ref: refs/heads/br2");
80+
81+
var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path);
82+
83+
Assert.Equal("1111111111111111111111111111111111111111", resolver.ResolveReference("ref: refs/heads/master"));
84+
Assert.Equal("2222222222222222222222222222222222222222", resolver.ResolveReference("ref: refs/heads/br1"));
85+
Assert.Equal("2222222222222222222222222222222222222222", resolver.ResolveReference("ref: refs/heads/br2"));
86+
}
87+
88+
[Fact]
89+
public void ReadPackedReferences()
90+
{
91+
var packedRefs =
92+
@"# pack-refs with:
93+
1111111111111111111111111111111111111111 refs/heads/master
94+
2222222222222222222222222222222222222222 refs/heads/br
95+
^3333333333333333333333333333333333333333
96+
4444444444444444444444444444444444444444 x
97+
5555555555555555555555555555555555555555 y
98+
6666666666666666666666666666666666666666 y z
99+
7777777777777777777777777777777777777777 refs/heads/br
100+
";
101+
102+
var actual = GitReferenceResolver.ReadPackedReferences(new StringReader(packedRefs), "<path>");
103+
104+
AssertEx.SetEqual(new[]
105+
{
106+
"refs/heads/br:2222222222222222222222222222222222222222",
107+
"refs/heads/master:1111111111111111111111111111111111111111"
108+
}, actual.Select(e => $"{e.Key}:{e.Value}"));
109+
}
110+
111+
[Theory]
112+
[InlineData("# pack-refs with:")]
113+
[InlineData("# pack-refs with:xyz")]
114+
[InlineData("# pack-refs with:xyz\n")]
115+
public void ReadPackedReferences_Empty(string content)
116+
{
117+
Assert.Empty(GitReferenceResolver.ReadPackedReferences(new StringReader(content), "<path>"));
118+
}
119+
120+
[Theory]
121+
[InlineData("")] // missing header
122+
[InlineData("# pack-refs with")] // invalid header prefix
123+
[InlineData("# pack-refs with:xyz\n1")] // bad object id
124+
[InlineData("# pack-refs with:xyz\n1111111111111111111111111111111111111111")] // no reference name
125+
[InlineData("# pack-refs with:xyz\n^1111111111111111111111111111111111111111")] // tag dereference without previous ref
126+
[InlineData("# pack-refs with:xyz\n1111111111111111111111111111111111111111 x\n^1")] // bad object id
127+
[InlineData("# pack-refs with:xyz\n^1111111111111111111111111111111111111111\n^2222222222222222222222222222222222222222")] // tag dereference without previous ref
128+
public void ReadPackedReferences_Errors(string content)
129+
{
130+
Assert.Throws<InvalidDataException>(() => GitReferenceResolver.ReadPackedReferences(new StringReader(content), "<path>"));
131+
}
132+
}
133+
}

src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -298,50 +298,6 @@ public void Submodules_Errors()
298298
}, diagnostics);
299299
}
300300

301-
[Fact]
302-
public void ResolveReference()
303-
{
304-
using var temp = new TempRoot();
305-
306-
var commonDir = temp.CreateDirectory();
307-
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");
308-
309-
refsHeadsDir.CreateFile("master").WriteAllText("0000000000000000000000000000000000000000");
310-
refsHeadsDir.CreateFile("br1").WriteAllText("ref: refs/heads/br2");
311-
refsHeadsDir.CreateFile("br2").WriteAllText("ref: refs/heads/master");
312-
313-
Assert.Equal("0123456789ABCDEFabcdef000000000000000000", GitRepository.ResolveReference("0123456789ABCDEFabcdef000000000000000000", commonDir.Path));
314-
315-
Assert.Equal("0000000000000000000000000000000000000000", GitRepository.ResolveReference("ref: refs/heads/master", commonDir.Path));
316-
Assert.Equal("0000000000000000000000000000000000000000", GitRepository.ResolveReference("ref: refs/heads/br1", commonDir.Path));
317-
Assert.Equal("0000000000000000000000000000000000000000", GitRepository.ResolveReference("ref: refs/heads/br2", commonDir.Path));
318-
319-
// branch without commits (emtpy repository) will have not file in refs/heads:
320-
Assert.Null(GitRepository.ResolveReference("ref: refs/heads/none", commonDir.Path));
321-
322-
Assert.Null(GitRepository.ResolveReference("ref: refs/heads/rec1 ", commonDir.Path));
323-
Assert.Null(GitRepository.ResolveReference("ref: refs/heads/none" + string.Join("/", Path.GetInvalidPathChars()), commonDir.Path));
324-
}
325-
326-
[Fact]
327-
public void ResolveReference_Errors()
328-
{
329-
using var temp = new TempRoot();
330-
331-
var commonDir = temp.CreateDirectory();
332-
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");
333-
334-
refsHeadsDir.CreateFile("rec1").WriteAllText("ref: refs/heads/rec2");
335-
refsHeadsDir.CreateFile("rec2").WriteAllText("ref: refs/heads/rec1");
336-
337-
Assert.Throws<InvalidDataException>(() => GitRepository.ResolveReference("ref: refs/heads/rec1", commonDir.Path));
338-
Assert.Throws<InvalidDataException>(() => GitRepository.ResolveReference("ref: xyz/heads/rec1", commonDir.Path));
339-
Assert.Throws<InvalidDataException>(() => GitRepository.ResolveReference("ref:refs/heads/rec1", commonDir.Path));
340-
Assert.Throws<InvalidDataException>(() => GitRepository.ResolveReference("refs/heads/rec1", commonDir.Path));
341-
Assert.Throws<InvalidDataException>(() => GitRepository.ResolveReference(new string('0', 39), commonDir.Path));
342-
Assert.Throws<InvalidDataException>(() => GitRepository.ResolveReference(new string('0', 41), commonDir.Path));
343-
}
344-
345301
[Fact]
346302
public void GetHeadCommitSha()
347303
{
@@ -376,7 +332,7 @@ public void GetSubmoduleHeadCommitSha()
376332
submoduleGitDir.CreateFile("HEAD").WriteAllText("ref: refs/heads/master");
377333

378334
var repository = new GitRepository(GitEnvironment.Empty, GitConfig.Empty, gitDir.Path, gitDir.Path, workingDir.Path);
379-
Assert.Equal("0000000000000000000000000000000000000000", repository.GetSubmoduleHeadCommitSha(submoduleWorkingDir.Path));
335+
Assert.Equal("0000000000000000000000000000000000000000", repository.ReadSubmoduleHeadCommitSha(submoduleWorkingDir.Path));
380336
}
381337
}
382338
}

src/Microsoft.Build.Tasks.Git/GitDataReader/CharUtils.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Microsoft.Build.Tasks.Git
77
internal static class CharUtils
88
{
99
public static char[] AsciiWhitespace = { ' ', '\t', '\n', '\f', '\r', '\v' };
10+
public static char[] WhitespaceSeparators = { ' ', '\t', '\f', '\v' };
1011

1112
public static bool IsHexadecimalDigit(char c)
1213
=> c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';

0 commit comments

Comments
 (0)