Skip to content
Draft
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
29 changes: 29 additions & 0 deletions src/Microsoft.Build.Tasks.Git.UnitTests/GitConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,5 +475,34 @@ public void RefStorage_Invalid(string value)
new GitConfig(ImmutableDictionary<GitVariableName, ImmutableArray<string>>.Empty
.Add(new GitVariableName("extensions", "", "refStorage"), [value])));
}

[Theory]
[InlineData(null, ObjectNameFormat.Sha1)]
[InlineData("sha1", ObjectNameFormat.Sha1)]
[InlineData("sha256", ObjectNameFormat.Sha256)]
internal void ParseObjectFormat(string? value, ObjectNameFormat expected)
{
var variables = ImmutableDictionary<GitVariableName, ImmutableArray<string>>.Empty;
if (value != null)
{
variables = variables.Add(new GitVariableName("extensions", "", "objectFormat"), [value]);
}

Assert.Equal(expected, new GitConfig(variables).ObjectNameFormat);
}

[Theory]
[InlineData("")]
[InlineData("Sha1")]
[InlineData("sha-1")]
[InlineData("sha-256")]
[InlineData("sha384")]
[InlineData("sha512")]
internal void ParseObjectFormat_Invalid(string value)
{
Assert.Throws<InvalidDataException>(() =>
new GitConfig(ImmutableDictionary<GitVariableName, ImmutableArray<string>>.Empty
.Add(new GitVariableName("extensions", "", "objectFormat"), [value])));
}
}
}
118 changes: 104 additions & 14 deletions src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,48 @@ public void ResolveReference()
refsHeadsDir.CreateFile("br1").WriteAllText("ref: refs/heads/br2");
refsHeadsDir.CreateFile("br2").WriteAllText("ref: refs/heads/master");

var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles);
using var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha1);

Assert.Equal("0123456789ABCDEFabcdef000000000000000000", resolver.ResolveReference("0123456789ABCDEFabcdef000000000000000000"));

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

// branch without commits (emtpy repository) will have not file in refs/heads:
// branch without commits (empty repository) will have not file in refs/heads:
Assert.Null(resolver.ResolveReference("ref: refs/heads/none"));

Assert.Null(resolver.ResolveReference("ref: refs/heads/rec1 "));
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");

using var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha256);

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

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 @@ -53,14 +80,24 @@ public void ResolveReference_Errors()
refsHeadsDir.CreateFile("rec1").WriteAllText("ref: refs/heads/rec2");
refsHeadsDir.CreateFile("rec2").WriteAllText("ref: refs/heads/rec1");

var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles);
using var resolver1 = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha1);

Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference("ref: refs/heads/rec1"));
Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference("ref: xyz/heads/rec1"));
Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference("ref:refs/heads/rec1"));
Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference("refs/heads/rec1"));

// Invalid SHA1 hash lengths
Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference(new string('0', ObjectNameFormat.Sha1.HashSize * 2 - 1)));
Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference(new string('0', ObjectNameFormat.Sha1.HashSize * 2 + 1)));
Assert.Throws<InvalidDataException>(() => resolver1.ResolveReference(new string('0', ObjectNameFormat.Sha256.HashSize * 2)));

Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref: refs/heads/rec1"));
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"));
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 39)));
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 41)));
using var resolver2 = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha256);

// Invalid SHA256 hash lengths
Assert.Throws<InvalidDataException>(() => resolver2.ResolveReference(new string('0', ObjectNameFormat.Sha256.HashSize * 2 - 1)));
Assert.Throws<InvalidDataException>(() => resolver2.ResolveReference(new string('0', ObjectNameFormat.Sha256.HashSize * 2 + 1)));
Assert.Throws<InvalidDataException>(() => resolver2.ResolveReference(new string('0', ObjectNameFormat.Sha1.HashSize * 2)));
}

[Fact]
Expand All @@ -80,13 +117,38 @@ 2222222222222222222222222222222222222222 refs/heads/br2

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

var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles);
using var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha1);

Assert.Equal("1111111111111111111111111111111111111111", resolver.ResolveReference("ref: refs/heads/master"));
Assert.Equal("2222222222222222222222222222222222222222", resolver.ResolveReference("ref: refs/heads/br1"));
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");

using var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha256);

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 @@ -101,7 +163,8 @@ 6666666666666666666666666666666666666666 y z
7777777777777777777777777777777777777777 refs/heads/br
";

var actual = GitReferenceResolver.ReadPackedReferences(new StringReader(packedRefs), "<path>");
using var resolver = new GitReferenceResolver(TempRoot.Root, TempRoot.Root, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha1);
var actual = resolver.ReadPackedReferences(new StringReader(packedRefs), "<path>");

AssertEx.SetEqual(new[]
{
Expand All @@ -110,26 +173,53 @@ 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
";

using var resolver = new GitReferenceResolver(TempRoot.Root, TempRoot.Root, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha256);
var actual = resolver.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")]
[InlineData("# pack-refs with:xyz\n")]
public void ReadPackedReferences_Empty(string content)
{
Assert.Empty(GitReferenceResolver.ReadPackedReferences(new StringReader(content), "<path>"));
using var resolver = new GitReferenceResolver(TempRoot.Root, TempRoot.Root, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha256);
Assert.Empty(resolver.ReadPackedReferences(new StringReader(content), "<path>"));
}

[Theory]
[InlineData("")] // missing header
[InlineData("# pack-refs with")] // invalid header prefix
[InlineData("# pack-refs with:xyz\n1")] // bad object id
[InlineData("# pack-refs with:xyz\n^2222222222222222222222222222222222222222222222222222222222222222")] // bad object id: sha256
[InlineData("# pack-refs with:xyz\n1111111111111111111111111111111111111111")] // no reference name
[InlineData("# pack-refs with:xyz\n^1111111111111111111111111111111111111111")] // tag dereference without previous ref
[InlineData("# pack-refs with:xyz\n1111111111111111111111111111111111111111 x\n^1")] // bad object id
[InlineData("# pack-refs with:xyz\n^1111111111111111111111111111111111111111\n^2222222222222222222222222222222222222222")] // tag dereference without previous ref
public void ReadPackedReferences_Errors(string content)
{
Assert.Throws<InvalidDataException>(() => GitReferenceResolver.ReadPackedReferences(new StringReader(content), "<path>"));
using var resolver = new GitReferenceResolver(TempRoot.Root, TempRoot.Root, ReferenceStorageFormat.LooseFiles, ObjectNameFormat.Sha1);
Assert.Throws<InvalidDataException>(() => resolver.ReadPackedReferences(new StringReader(content), "<path>"));
}

[Fact]
Expand All @@ -150,7 +240,7 @@ public void ResolveReference_RefTable()
var ref1 = refTableDir.CreateFile("1.ref").WriteAllBytes(GitRefTableTestWriter.GetRefTableBlob([("refs/heads/a", 0x01), ("refs/heads/c", 0x02)]));
TempFile ref2;

using (var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.RefTable))
using (var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path, ReferenceStorageFormat.RefTable, ObjectNameFormat.Sha1))
{
Assert.Equal("0100000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/a"));
Assert.Equal("0200000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/c"));
Expand Down
50 changes: 44 additions & 6 deletions src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,27 @@ public void TryFindRepository_Worktree_Realistic()
{
using var temp = new TempRoot();

var mainWorkingDir = temp.CreateDirectory();
var repoDir = temp.CreateDirectory();

var mainWorkingDir = repoDir.CreateDirectory("main");
var mainWorkingSubDir = mainWorkingDir.CreateDirectory("A");
var mainGitDir = mainWorkingDir.CreateDirectory(".git");
mainGitDir.CreateFile("HEAD");

var worktreesDir = mainGitDir.CreateDirectory("worktrees");
var worktreeGitDir = worktreesDir.CreateDirectory("myworktree");
var worktreeGitSubDir = worktreeGitDir.CreateDirectory("B");
var worktreeDir = temp.CreateDirectory();
var worktreeDir = repoDir.CreateDirectory("worktree");
var worktreeSubDir = worktreeDir.CreateDirectory("C");
var worktreeGitFile = worktreeDir.CreateFile(".git").WriteAllText("gitdir: " + worktreeGitDir + " \r\n\t\v");

// test relative path to work tree dir:
var worktreeGitFile = worktreeDir.CreateFile(".git").WriteAllText("gitdir: ../main/.git/worktrees/myworktree \r\n\t\v");

worktreeGitDir.CreateFile("HEAD");
worktreeGitDir.CreateFile("commondir").WriteAllText("../..\n");
worktreeGitDir.CreateFile("gitdir").WriteAllText(worktreeGitFile.Path + " \r\n\t\v");

// test relative path to work tree .git file:
worktreeGitDir.CreateFile("gitdir").WriteAllText("../../../../worktree/.git \r\n\t\v");

// start under main repository directory:
Assert.True(GitRepository.TryFindRepository(mainWorkingSubDir.Path, out var location));
Expand Down Expand Up @@ -226,6 +232,8 @@ public void OpenRepository_Version1_Extensions()
preciousObjects = true
partialClone = promisor_remote
worktreeConfig = true
relativeWorktrees = true
objectformat = sha256
");

Assert.True(GitRepository.TryFindRepository(gitDir.Path, out var location));
Expand Down Expand Up @@ -264,6 +272,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 Expand Up @@ -307,7 +345,7 @@ public void OpenRepository_Worktree_GitdirFileMissing()
Assert.Equal(worktreeGitDir.Path, location.GitDirectory);
Assert.Equal(mainGitDir.Path, location.CommonDirectory);
Assert.Equal(worktreeDir.Path, location.WorkingDirectory);

var repository = GitRepository.OpenRepository(location, GitEnvironment.Empty);
Assert.Equal(repository.GitDirectory, location.GitDirectory);
Assert.Equal(repository.CommonDirectory, location.CommonDirectory);
Expand Down Expand Up @@ -346,7 +384,7 @@ public void OpenRepository_Worktree_GitdirFileDifferentPath()
var repository = GitRepository.OpenRepository(location, GitEnvironment.Empty);
Assert.Equal(repository.GitDirectory, location.GitDirectory);
Assert.Equal(repository.CommonDirectory, location.CommonDirectory);

// actual working dir is not affected:
Assert.Equal(worktreeDir.Path, location.WorkingDirectory);
}
Expand Down
Loading
Loading