Skip to content

Commit 1e57847

Browse files
committed
Fixes
1 parent 7fa8405 commit 1e57847

File tree

6 files changed

+121
-38
lines changed

6 files changed

+121
-38
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ private static GitConfig LoadFromString(string gitDirectory, string configPath,
2020
=> new GitConfig.Reader(gitDirectory, gitDirectory, GitEnvironment.Empty, _ => new StringReader(configContent)).
2121
LoadFrom(configPath);
2222

23+
[Theory]
24+
[InlineData("sha1", ObjectFormat.Sha1)]
25+
[InlineData("sha256", ObjectFormat.Sha256)]
26+
internal void TryParseObjectFormat_Valid(string str, ObjectFormat expected)
27+
{
28+
Assert.Equal(expected, GitConfig.ParseObjectFormat(str));
29+
}
30+
31+
[Theory]
32+
[InlineData("")]
33+
[InlineData("Sha1")]
34+
[InlineData("sha-1")]
35+
[InlineData("sha-256")]
36+
[InlineData("sha384")]
37+
[InlineData("sha512")]
38+
internal void TryParseObjectFormat_Invalid(string str)
39+
{
40+
Assert.Throws<InvalidDataException>(() => GitConfig.ParseObjectFormat(str));
41+
}
42+
2343
[Fact]
2444
public void Sections()
2545
{

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public void ResolveReference()
3333
Assert.Equal("0000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br1"));
3434
Assert.Equal("0000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br2"));
3535

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

3939
Assert.Null(resolver.ResolveReference("ref: refs/heads/rec1 "));
@@ -58,8 +58,9 @@ public void ResolveReference_SHA256()
5858
var resolver = new GitReferenceResolver(gitDir.Path, commonDir.Path);
5959

6060
// Verify SHA256 hash is accepted directly
61-
Assert.Equal("0123456789ABCDEFabcdef00000000000000000000000000000000000000000000",
62-
resolver.ResolveReference("0123456789ABCDEFabcdef00000000000000000000000000000000000000000000"));
61+
Assert.Equal(
62+
"0123456789ABCDEFabcdef00000000000000000000000000000000000000000000",
63+
resolver.ResolveReference("0123456789ABCDEFabcdef00000000000000000000000000000000000000000000"));
6364

6465
Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/master"));
6566
Assert.Equal("0000000000000000000000000000000000000000000000000000000000000000", resolver.ResolveReference("ref: refs/heads/br1"));
@@ -85,9 +86,11 @@ public void ResolveReference_Errors()
8586
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref: xyz/heads/rec1"));
8687
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("ref:refs/heads/rec1"));
8788
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference("refs/heads/rec1"));
89+
8890
// Invalid SHA1 hash lengths
8991
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 39)));
9092
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 41)));
93+
9194
// Invalid SHA256 hash lengths
9295
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 63)));
9396
Assert.Throws<InvalidDataException>(() => resolver.ResolveReference(new string('0', 65)));

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

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,67 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Collections.Immutable;
8-
using System.Diagnostics;
9-
using System.Diagnostics.CodeAnalysis;
108
using System.IO;
119
using System.Linq;
12-
using System.Text;
10+
using System.Runtime.Serialization;
1311

1412
namespace Microsoft.Build.Tasks.Git
1513
{
1614
internal sealed partial class GitConfig
1715
{
1816
public static readonly GitConfig Empty = new GitConfig(ImmutableDictionary<GitVariableName, ImmutableArray<string>>.Empty);
1917

18+
private const int SupportedGitRepoFormatVersion = 1;
19+
20+
public const string CoreSectionName = "core";
21+
public const string ExtensionsSectionName = "extensions";
22+
23+
public const string ObjectFormatVariableName = "objectFormat";
24+
public const string RepositoryFormatVersionVariableName = "repositoryformatversion";
25+
26+
private static readonly ImmutableArray<string> s_knownExtensions = ["noop", "preciousObjects", "partialclone", "worktreeConfig", ObjectFormatVariableName];
27+
2028
public readonly ImmutableDictionary<GitVariableName, ImmutableArray<string>> Variables;
2129

30+
/// <summary>
31+
/// The parsed value of "extensions.objectFormat" variable.
32+
/// </summary>
33+
public ObjectFormat ObjectFormat { get; }
34+
35+
/// <exception cref="InvalidDataException"/>
2236
public GitConfig(ImmutableDictionary<GitVariableName, ImmutableArray<string>> variables)
2337
{
2438
NullableDebug.Assert(variables != null);
2539
Variables = variables;
40+
41+
ObjectFormat = GetVariableValue(ExtensionsSectionName, ObjectFormatVariableName) is { } objectFormatStr
42+
? ParseObjectFormat(objectFormatStr)
43+
: ObjectFormat.Sha1;
44+
}
45+
46+
/// <exception cref="NotSupportedException"/>
47+
internal void ValidateFormat()
48+
{
49+
// See https://github.com/git/git/blob/master/Documentation/technical/repository-version.txt
50+
string? versionStr = GetVariableValue(CoreSectionName, RepositoryFormatVersionVariableName);
51+
if (TryParseInt64Value(versionStr, out var version) && version > SupportedGitRepoFormatVersion)
52+
{
53+
throw new NotSupportedException(string.Format(Resources.UnsupportedRepositoryVersion, versionStr, SupportedGitRepoFormatVersion));
54+
}
55+
56+
if (version == 1)
57+
{
58+
// All variables defined under extensions section must be known, otherwise a git implementation is not allowed to proced.
59+
foreach (var variable in Variables)
60+
{
61+
if (variable.Key.SectionNameEquals(ExtensionsSectionName) &&
62+
!s_knownExtensions.Contains(variable.Key.VariableName, StringComparer.OrdinalIgnoreCase))
63+
{
64+
throw new NotSupportedException(string.Format(
65+
Resources.UnsupportedRepositoryExtension, variable.Key.VariableName, string.Join(", ", s_knownExtensions)));
66+
}
67+
}
68+
}
2669
}
2770

2871
// for testing:
@@ -125,5 +168,14 @@ internal static bool TryParseInt64Value(string? str, out long value)
125168

126169
return true;
127170
}
171+
172+
// internal for testing
173+
internal static ObjectFormat ParseObjectFormat(string value)
174+
=> value switch
175+
{
176+
"sha1" => ObjectFormat.Sha1,
177+
"sha256" => ObjectFormat.Sha256,
178+
_ => throw new InvalidDataException(),
179+
};
128180
}
129181
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@ internal sealed class GitReferenceResolver
2121

2222
private readonly string _commonDirectory;
2323
private readonly string _gitDirectory;
24+
private readonly ObjectFormat _objectFormat;
2425

2526
// maps refs/heads references to the correspondign object ids:
2627
private readonly Lazy<ImmutableDictionary<string, string>> _lazyPackedReferences;
2728

28-
public GitReferenceResolver(string gitDirectory, string commonDirectory)
29+
public GitReferenceResolver(string gitDirectory, string commonDirectory, ObjectFormat objectFormat)
2930
{
3031
Debug.Assert(PathUtils.IsNormalized(gitDirectory));
3132
Debug.Assert(PathUtils.IsNormalized(commonDirectory));
3233

3334
_gitDirectory = gitDirectory;
3435
_commonDirectory = commonDirectory;
36+
_objectFormat = objectFormat;
3537
_lazyPackedReferences = new Lazy<ImmutableDictionary<string, string>>(() => ReadPackedReferences(_gitDirectory));
3638
}
3739

38-
private static ImmutableDictionary<string, string> ReadPackedReferences(string gitDirectory)
40+
private ImmutableDictionary<string, string> ReadPackedReferences(string gitDirectory)
3941
{
4042
// https://git-scm.com/docs/git-pack-refs
4143

@@ -62,7 +64,7 @@ private static ImmutableDictionary<string, string> ReadPackedReferences(string g
6264
}
6365

6466
// internal for testing
65-
internal static ImmutableDictionary<string, string> ReadPackedReferences(TextReader reader, string path)
67+
internal ImmutableDictionary<string, string> ReadPackedReferences(TextReader reader, string path)
6668
{
6769
var builder = ImmutableDictionary.CreateBuilder<string, string>();
6870

@@ -254,8 +256,7 @@ internal static string ReadReferenceFromFile(string path)
254256
private string? ResolvePackedReference(string reference)
255257
=> _lazyPackedReferences.Value.TryGetValue(reference, out var objectId) ? objectId : null;
256258

257-
// SHA1 hashes are 40 hex characters, SHA256 hashes are 64 hex characters
258-
private static bool IsObjectId(string reference)
259-
=> (reference.Length == 40 || reference.Length == 64) && reference.All(CharUtils.IsHexadecimalDigit);
259+
private bool IsObjectId(string reference)
260+
=> reference.Length == _objectFormat.HashLength * 2 && reference.All(CharUtils.IsHexadecimalDigit);
260261
}
261262
}

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

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ namespace Microsoft.Build.Tasks.Git
1313
{
1414
internal sealed class GitRepository
1515
{
16-
private const int SupportedGitRepoFormatVersion = 1;
17-
1816
private const string CommonDirFileName = "commondir";
1917
private const string GitDirName = ".git";
2018
private const string GitDirPrefix = "gitdir: ";
@@ -24,9 +22,6 @@ internal sealed class GitRepository
2422

2523
private const string GitModulesFileName = ".gitmodules";
2624

27-
private static readonly ImmutableArray<string> s_knownExtensions =
28-
ImmutableArray.Create("noop", "preciousObjects", "partialclone", "worktreeConfig", "objectformat");
29-
3025
public GitConfig Config { get; }
3126

3227
public GitIgnore Ignore => _lazyIgnore.Value;
@@ -68,7 +63,7 @@ internal GitRepository(GitEnvironment environment, GitConfig config, string gitD
6863
WorkingDirectory = workingDirectory;
6964
Environment = environment;
7065

71-
_referenceResolver = new GitReferenceResolver(gitDirectory, commonDirectory);
66+
_referenceResolver = new GitReferenceResolver(gitDirectory, commonDirectory, config.ObjectFormat);
7267
_lazySubmodules = new Lazy<(ImmutableArray<GitSubmodule>, ImmutableArray<string>)>(ReadSubmodules);
7368
_lazyIgnore = new Lazy<GitIgnore>(LoadIgnore);
7469
_lazyHeadCommitSha = new Lazy<string?>(ReadHeadCommitSha);
@@ -121,29 +116,10 @@ public static GitRepository OpenRepository(GitRepositoryLocation location, GitEn
121116

122117
var reader = new GitConfig.Reader(location.GitDirectory, location.CommonDirectory, environment);
123118
var config = reader.Load();
119+
config.ValidateFormat();
124120

125121
var workingDirectory = GetWorkingDirectory(config, location);
126122

127-
// See https://github.com/git/git/blob/master/Documentation/technical/repository-version.txt
128-
string? versionStr = config.GetVariableValue("core", "repositoryformatversion");
129-
if (GitConfig.TryParseInt64Value(versionStr, out var version) && version > SupportedGitRepoFormatVersion)
130-
{
131-
throw new NotSupportedException(string.Format(Resources.UnsupportedRepositoryVersion, versionStr, SupportedGitRepoFormatVersion));
132-
}
133-
134-
if (version == 1)
135-
{
136-
// All variables defined under extensions section must be known, otherwise a git implementation is not allowed to proced.
137-
foreach (var variable in config.Variables)
138-
{
139-
if (variable.Key.SectionNameEquals("extensions") && !s_knownExtensions.Contains(variable.Key.VariableName, StringComparer.OrdinalIgnoreCase))
140-
{
141-
throw new NotSupportedException(string.Format(
142-
Resources.UnsupportedRepositoryExtension, variable.Key.VariableName, string.Join(", ", s_knownExtensions)));
143-
}
144-
}
145-
}
146-
147123
return new GitRepository(environment, config, location.GitDirectory, location.CommonDirectory, workingDirectory);
148124
}
149125

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the.NET Foundation under one or more agreements.
2+
// The.NET Foundation licenses this file to you under the MIT license.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
7+
namespace Microsoft.Build.Tasks.Git;
8+
9+
/// <summary>
10+
/// Format of object names.
11+
/// https://git-scm.com/docs/hash-function-transition.html#_object_format
12+
/// </summary>
13+
internal enum ObjectFormat
14+
{
15+
Sha1 = 1,
16+
Sha256 = 2,
17+
}
18+
19+
internal static class ObjectFormatExtensions
20+
{
21+
extension(ObjectFormat self)
22+
{
23+
public int HashLength
24+
=> self switch
25+
{
26+
ObjectFormat.Sha1 => 20,
27+
ObjectFormat.Sha256 => 32,
28+
_ => throw new InvalidOperationException()
29+
};
30+
}
31+
}

0 commit comments

Comments
 (0)