Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ public void ResolveReference()
Assert.Null(resolver.ResolveReference("ref: refs/heads/none" + string.Join("/", Path.GetInvalidPathChars())));
}

[Fact]
public void ResolveReference_SHA256()
{
using var temp = new TempRoot();

var gitDir = temp.CreateDirectory();

var commonDir = temp.CreateDirectory();
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");

// SHA256 hash (64 characters)
refsHeadsDir.CreateFile("master").WriteAllText("0000000000000000000000000000000000000000000000000000000000000000");
refsHeadsDir.CreateFile("br1").WriteAllText("ref: refs/heads/br2");
refsHeadsDir.CreateFile("br2").WriteAllText("ref: refs/heads/master");

var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path);

// Verify SHA256 hash is accepted directly
Assert.Equal("0123456789ABCDEFabcdef000000000000000000000000000000000000000000",
resolver.ResolveReference("0123456789ABCDEFabcdef000000000000000000000000000000000000000000"));

Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/master"));
Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br1"));
Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br2"));
}

[Fact]
public void ResolveReference_Errors()
{
Expand All @@ -59,8 +85,12 @@ public void ResolveReference_Errors()
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref: xyz/heads/rec1"));
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref:refs/heads/rec1"));
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("refs/heads/rec1"));
// Invalid SHA1 hash lengths
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 39)));
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 41)));
// Invalid SHA256 hash lengths
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 63)));
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 65)));
}

[Fact]
Expand All @@ -87,6 +117,31 @@ 2222222222222222222222222222222222222222 refs/heads/br2
Assert.Equal("2222222222222222222222222222222222222222", resolver.ResolveReference("ref: refs/heads/br2"));
}

[Fact]
public void ResolveReference_Packed_SHA256()
{
using var temp = new TempRoot();

var gitDir = temp.CreateDirectory();

// Packed refs with SHA256 hashes (64 characters)
gitDir.CreateFile("packed-refs").WriteAllText(
@"# pack-refs with: peeled fully-peeled sorted
1111111111111111111111111111111111111111111111111111111111111111 refs/heads/master
2222222222222222222222222222222222222222222222222222222222222222 refs/heads/br2
");
var commonDir = temp.CreateDirectory();
var refsHeadsDir = commonDir.CreateDirectory("refs").CreateDirectory("heads");

refsHeadsDir.CreateFile("br1").WriteAllText("ref: refs/heads/br2");

var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path);

Assert.Equal("1111111111111111111111111111111111111111111111111111111111111111", resolver.ResolveReference("ref: refs/heads/master"));
Assert.Equal("2222222222222222222222222222222222222222222222222222222222222222", resolver.ResolveReference("ref: refs/heads/br1"));
Assert.Equal("2222222222222222222222222222222222222222222222222222222222222222", resolver.ResolveReference("ref: refs/heads/br2"));
}

[Fact]
public void ReadPackedReferences()
{
Expand All @@ -110,6 +165,29 @@ 7777777777777777777777777777777777777777 refs/heads/br
}, actual.Select(e => $"{e.Key}:{e.Value}"));
}

[Fact]
public void ReadPackedReferences_SHA256()
{
var packedRefs =
@"# pack-refs with:
1111111111111111111111111111111111111111111111111111111111111111 refs/heads/master
2222222222222222222222222222222222222222222222222222222222222222 refs/heads/br
^3333333333333333333333333333333333333333333333333333333333333333
4444444444444444444444444444444444444444444444444444444444444444 x
5555555555555555555555555555555555555555555555555555555555555555 y
6666666666666666666666666666666666666666666666666666666666666666 y z
7777777777777777777777777777777777777777777777777777777777777777 refs/heads/br
";

var actual = GitReferenceResolver.ReadPackedReferences(new StringReader(packedRefs), "<path>");

AssertEx.SetEqual(new[]
{
"refs/heads/br:2222222222222222222222222222222222222222222222222222222222222222",
"refs/heads/master:1111111111111111111111111111111111111111111111111111111111111111"
}, actual.Select(e => $"{e.Key}:{e.Value}"));
}

[Theory]
[InlineData("# pack-refs with:")]
[InlineData("# pack-refs with:xyz")]
Expand Down
31 changes: 31 additions & 0 deletions src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ public void OpenRepository_Version1_Extensions()
preciousObjects = true
partialClone = promisor_remote
worktreeConfig = true
objectformat = sha256
");

Assert.True(GitRepository.TryFindRepository(gitDir.Path, out var location));
Expand Down Expand Up @@ -264,6 +265,36 @@ public void OpenRepository_Version1_UnknownExtension()
Assert.Throws<NotSupportedException>(() => GitRepository.OpenRepository(src.Path, new GitEnvironment(homeDir.Path)));
}

[Fact]
public void OpenRepository_Version1_ObjectFormatExtension()
{
using var temp = new TempRoot();

var homeDir = temp.CreateDirectory();

var workingDir = temp.CreateDirectory();
var gitDir = workingDir.CreateDirectory(".git");

gitDir.CreateFile("HEAD").WriteAllText("ref: refs/heads/master");
gitDir.CreateDirectory("refs").CreateDirectory("heads").CreateFile("master").WriteAllText("0000000000000000000000000000000000000000");
gitDir.CreateDirectory("objects");

gitDir.CreateFile("config").WriteAllText(@"
[core]
repositoryformatversion = 1
[extensions]
objectformat = sha256");

var src = workingDir.CreateDirectory("src");

// Should not throw - objectformat extension should be supported
var repository = GitRepository.OpenRepository(src.Path, new GitEnvironment(homeDir.Path));
Assert.NotNull(repository);
Assert.Equal(gitDir.Path, repository.GitDirectory);
Assert.Equal(gitDir.Path, repository.CommonDirectory);
Assert.Equal(workingDir.Path, repository.WorkingDirectory);
}

[Fact]
public void OpenRepository_VersionNotSupported()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ internal static string ReadReferenceFromFile(string path)
private string? ResolvePackedReference(string reference)
=> _lazyPackedReferences.Value.TryGetValue(reference, out var objectId) ? objectId : null;

// SHA1 hashes are 40 hex characters, SHA256 hashes are 64 hex characters
private static bool IsObjectId(string reference)
=> reference.Length == 40 && reference.All(CharUtils.IsHexadecimalDigit);
=> (reference.Length == 40 || reference.Length == 64) && reference.All(CharUtils.IsHexadecimalDigit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal sealed class GitRepository
private const string GitModulesFileName = ".gitmodules";

private static readonly ImmutableArray<string> s_knownExtensions =
ImmutableArray.Create("noop", "preciousObjects", "partialclone", "worktreeConfig");
ImmutableArray.Create("noop", "preciousObjects", "partialclone", "worktreeConfig", "objectformat");

public GitConfig Config { get; }

Expand Down