From 59d586026d207e75ff4ab923d268e2d226bd9dfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 01:02:34 +0000 Subject: [PATCH 1/5] Initial plan From 7cfd3403ce5051eba88499a7265623f0ff6b349f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 25 Sep 2025 01:10:36 +0000 Subject: [PATCH 2/5] Add objectformat extension support for SHA256 repositories Co-authored-by: tmat <41759+tmat@users.noreply.github.com> --- .../GitRepositoryTests.cs | 31 +++++++++++++++++++ .../GitDataReader/GitRepository.cs | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs index 7ba9993d..7cc4eb83 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs @@ -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)); @@ -264,6 +265,36 @@ public void OpenRepository_Version1_UnknownExtension() Assert.Throws(() => 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() { diff --git a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs index fc7446ea..7186b258 100644 --- a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs +++ b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs @@ -25,7 +25,7 @@ internal sealed class GitRepository private const string GitModulesFileName = ".gitmodules"; private static readonly ImmutableArray s_knownExtensions = - ImmutableArray.Create("noop", "preciousObjects", "partialclone", "worktreeConfig"); + ImmutableArray.Create("noop", "preciousObjects", "partialclone", "worktreeConfig", "objectformat"); public GitConfig Config { get; } From 0a21354de650a84088ad88da114f4b6f273b8f0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:14:40 +0000 Subject: [PATCH 3/5] Accept SHA256 hashes (64 characters) in addition to SHA1 hashes (40 characters) Co-authored-by: tmat <41759+tmat@users.noreply.github.com> --- .../GitReferenceResolverTests.cs | 78 +++++++++++++++++++ .../GitDataReader/GitReferenceResolver.cs | 2 +- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs index c61fb555..d2ab727d 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs @@ -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("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() { @@ -59,8 +85,12 @@ public void ResolveReference_Errors() Assert.Throws(() => resolver.ResolveReference("ref: xyz/heads/rec1")); Assert.Throws(() => resolver.ResolveReference("ref:refs/heads/rec1")); Assert.Throws(() => resolver.ResolveReference("refs/heads/rec1")); + // Invalid SHA1 hash lengths Assert.Throws(() => resolver.ResolveReference(new string('0', 39))); Assert.Throws(() => resolver.ResolveReference(new string('0', 41))); + // Invalid SHA256 hash lengths + Assert.Throws(() => resolver.ResolveReference(new string('0', 63))); + Assert.Throws(() => resolver.ResolveReference(new string('0', 65))); } [Fact] @@ -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() { @@ -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), ""); + + 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")] diff --git a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs index 7d970c09..7a6f9184 100644 --- a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs +++ b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs @@ -255,6 +255,6 @@ internal static string ReadReferenceFromFile(string path) => _lazyPackedReferences.Value.TryGetValue(reference, out var objectId) ? objectId : null; private static bool IsObjectId(string reference) - => reference.Length == 40 && reference.All(CharUtils.IsHexadecimalDigit); + => (reference.Length == 40 || reference.Length == 64) && reference.All(CharUtils.IsHexadecimalDigit); } } From a6d6ac1fb377ce56923a914767067744f3ae961a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:51:44 +0000 Subject: [PATCH 4/5] Add comment explaining SHA1 (40) and SHA256 (64) hash lengths Co-authored-by: tmat <41759+tmat@users.noreply.github.com> --- .../GitDataReader/GitReferenceResolver.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs index 7a6f9184..54d6fb1c 100644 --- a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs +++ b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitReferenceResolver.cs @@ -254,6 +254,7 @@ 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.Length == 64) && reference.All(CharUtils.IsHexadecimalDigit); } From 2571555c4420ac49b699dc74e478676e04ce0a62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:03:51 +0000 Subject: [PATCH 5/5] Fix ResolveReference_SHA256 test - correct hash to be exactly 64 characters Co-authored-by: tmat <41759+tmat@users.noreply.github.com> --- .../GitReferenceResolverTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs index d2ab727d..bfdc6edb 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitReferenceResolverTests.cs @@ -58,8 +58,8 @@ public void ResolveReference_SHA256() var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path); // Verify SHA256 hash is accepted directly - Assert.Equal("0123456789ABCDEFabcdef00000000000000000000000000000000000000000000", - resolver.ResolveReference("0123456789ABCDEFabcdef00000000000000000000000000000000000000000000")); + Assert.Equal("0123456789ABCDEFabcdef000000000000000000000000000000000000000000", + resolver.ResolveReference("0123456789ABCDEFabcdef000000000000000000000000000000000000000000")); Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/master")); Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br1"));