diff --git a/gitc b/gitc new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitConfigTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitConfigTests.cs index 1fafa492a..579796fda 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitConfigTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitConfigTests.cs @@ -48,11 +48,11 @@ public void Sections() [Fact] public void Sections_Errors() { - Assert.Throws(() => LoadFromString("/", "/config", @" + var e = Assert.Throws(() => LoadFromString("/", "/config", @" [s] a = 1")); - + AssertEx.AreEqual(string.Format(Resources.ErrorParsingConfigLineInFile, 4, "/config", string.Format(Resources.UnexpectedCharacter, "U+0031")), e.Message); } [Fact] @@ -206,7 +206,9 @@ TextReader openFile(string path) }); } - Assert.Throws(() => new GitConfig.Reader(gitDirectory, gitDirectory, GitEnvironment.Empty, openFile).LoadFrom(Path.Combine(gitDirectory, "config"))); + var e = Assert.Throws(() => new GitConfig.Reader(gitDirectory, gitDirectory, GitEnvironment.Empty, openFile).LoadFrom(Path.Combine(gitDirectory, "config"))); + + AssertEx.AreEqual(string.Format(Resources.ConfigurationFileRecursionExceededMaximumAllowedDepth, 10), e.Message); } [Theory] @@ -286,22 +288,31 @@ public void HierarchicalLoad(bool enableProgramData, bool enableXdg, string expe [InlineData("[X \"/*-\\a\"]", "x", "/*-a")] public void ReadSectionHeader(string str, string name, string subsectionName) { - GitConfig.Reader.ReadSectionHeader(new StringReader(str), new StringBuilder(), out var actualName, out var actualSubsectionName); + GitConfig.Reader.ReadSectionHeader( + new GitConfig.LineCountingReader(new StringReader(str), "path"), new StringBuilder(), out var actualName, out var actualSubsectionName); + Assert.Equal(name, actualName); Assert.Equal(subsectionName, actualSubsectionName); } [Theory] - [InlineData("[")] - [InlineData("[x")] - [InlineData("[x x x]")] - [InlineData("[* \"\\")] - [InlineData("[* \"\\\"]")] - [InlineData("[* \"*\"]")] - [InlineData("[x \"y\" ]")] - public void ReadSectionHeader_Errors(string str) + [InlineData("[", -1)] + [InlineData("[x", -1)] + [InlineData("[x x x]", 'x')] + [InlineData("[* \"\\", '*')] + [InlineData("[* \"\\\"]", '*')] + [InlineData("[* \"*\"]", '*')] + [InlineData("[x \"y\" ]", ' ')] + public void ReadSectionHeader_Errors(string str, int unexpectedChar) { - Assert.Throws(() => GitConfig.Reader.ReadSectionHeader(new StringReader(str), new StringBuilder(), out _, out _)); + var e = Assert.Throws(() => GitConfig.Reader.ReadSectionHeader( + new GitConfig.LineCountingReader(new StringReader(str), "path"), new StringBuilder(), out _, out _)); + + var message = (unexpectedChar == -1) + ? Resources.UnexpectedEndOfFile + : string.Format(Resources.UnexpectedCharacter, $@"U+{unexpectedChar:x4}"); + + AssertEx.AreEqual(string.Format(Resources.ErrorParsingConfigLineInFile, 1, "path", message), e.Message); } [Theory] @@ -336,29 +347,44 @@ public void ReadSectionHeader_Errors(string str) [InlineData("name= 1\\\"", "name", "1\"")] [InlineData("name= ", "name", "")] [InlineData("name=", "name", "")] + [InlineData("name=\"a\rb\"", "name", "a\rb")] + [InlineData("name=\"a\nb\"", "name", "a\nb")] + [InlineData("name=\"a\r\nb\"", "name", "a\r\nb")] public void ReadVariableDeclaration(string str, string name, string value) { - GitConfig.Reader.ReadVariableDeclaration(new StringReader(str), new StringBuilder(), out var actualName, out var actualValue); + GitConfig.Reader.ReadVariableDeclaration( + new GitConfig.LineCountingReader(new StringReader(str), "path"), new StringBuilder(), out var actualName, out var actualValue); + Assert.Equal(name, actualName); Assert.Equal(value, actualValue); } [Theory] - [InlineData("")] - [InlineData("*")] - [InlineData("-=1")] - [InlineData("_=1")] - [InlineData("5=1")] - [InlineData("a_=1")] - [InlineData("a*=1")] - [InlineData("name=\\j")] - [InlineData("name=\"")] - [InlineData("name=\"a")] - [InlineData("name=\"a\n")] - [InlineData("name=\"a\nb")] - public void ReadVariableDeclaration_Errors(string str) + [InlineData("", -1)] + [InlineData("*", '*')] + [InlineData("-=1", '-')] + [InlineData("_=1", '_')] + [InlineData("5=1", '5')] + [InlineData("a_=1", '_')] + [InlineData("a*=1", '*')] + [InlineData("name=\\j", '\\')] + [InlineData("name=\"", -1)] + [InlineData("name=\"a", -1)] + [InlineData("name=\"a\n", -1, 2)] + [InlineData("name=\"a\nb", -1, 2)] + [InlineData("name=\"a\rb", -1, 2)] + [InlineData("name=\"a\r\nb", -1, 2)] + [InlineData("name=\"a\r\rb", -1, 3)] + public void ReadVariableDeclaration_Errors(string str, int unexpectedChar, int line = 1) { - Assert.Throws(() => GitConfig.Reader.ReadVariableDeclaration(new StringReader(str), new StringBuilder(), out _, out _)); + var e = Assert.Throws(() => GitConfig.Reader.ReadVariableDeclaration( + new GitConfig.LineCountingReader(new StringReader(str), "path"), new StringBuilder(), out _, out _)); + + var message = (unexpectedChar == -1) + ? Resources.UnexpectedEndOfFile + : string.Format(Resources.UnexpectedCharacter, $@"U+{unexpectedChar:x4}"); + + AssertEx.AreEqual(string.Format(Resources.ErrorParsingConfigLineInFile, line, "path", message), e.Message); } [Theory] diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitDataTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitDataTests.cs index 6eac1920d..921117c1c 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitDataTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitDataTests.cs @@ -54,7 +54,7 @@ public void MinimalGitData() Assert.Equal("1111111111111111111111111111111111111111", repository.GetHeadCommitSha()); var warnings = new List<(string, object?[])>(); - var sourceRoots = GitOperations.GetSourceRoots(repository, remoteName: null, warnOnMissingCommit: true, (message, args) => warnings.Add((message, args))); + var sourceRoots = GitOperations.GetSourceRoots(repository, remoteName: null, warnOnMissingCommitOrUnsupportedUri: true, (message, args) => warnings.Add((message, args))); AssertEx.Equal(new[] { $@"'{repoDir.Path}{s}' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' ScmRepositoryUrl='http://github.com/test-org/test-repo'", diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs index 115f5809c..7a9122d2c 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs @@ -192,147 +192,33 @@ public void GetRepositoryUrl_InsteadOf() Assert.Empty(warnings); } - /// - /// Test scenario where a local repository is cloned from another local repository that was cloned from a remote repository. - /// [Theory] - [InlineData(true)] - [InlineData(false)] - public void GetRepositoryUrl_Local(bool useFileUrl) + [InlineData("local")] + [InlineData("file")] + [InlineData("xyz://a/b")] + public void GetRepositoryUrl_UnsupportedUrl(string kind) { using var temp = new TempRoot(); var dir = temp.CreateDirectory(); - var mainWorkingDir = dir.CreateDirectory("x \u1234"); - var mainGitDir = mainWorkingDir.CreateDirectory(".git"); - mainGitDir.CreateFile("HEAD"); - mainGitDir.CreateFile("config").WriteAllText(@"[remote ""origin""] url = http://github.com/repo"); + var originRepoPath = dir.CreateDirectory("x \u1234").Path; - var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] - { - KVP(new GitVariableName("remote", "origin", "url"), - ImmutableArray.Create(useFileUrl ? new Uri(mainWorkingDir.Path).AbsolutePath : mainWorkingDir.Path)), - }))); - - var warnings = new List<(string, object?[])>(); - Assert.Equal("http://github.com/repo", GitOperations.GetRepositoryUrl(repo, remoteName: null, logWarning: (message, args) => warnings.Add((message, args)))); - Assert.Empty(warnings); - } - - /// - /// Test scenario where a local repository is cloned from another local repository that was cloned from a remote repository. - /// With custom remote name. - /// - [Fact] - public void GetRepositoryUrl_Local_CustomRemoteName() - { - using var temp = new TempRoot(); - - var mainWorkingDir = temp.CreateDirectory(); - var mainGitDir = mainWorkingDir.CreateDirectory(".git"); - mainGitDir.CreateFile("HEAD"); - mainGitDir.CreateFile("config").WriteAllText(@"[remote ""origin""] url = http://github.com/repo"); - - var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] - { - KVP(new GitVariableName("remote", "origin", "url"), ImmutableArray.Create(mainWorkingDir.Path)), - }))); - - var warnings = new List<(string, object?[])>(); - Assert.Equal("http://github.com/repo", GitOperations.GetRepositoryUrl(repo, remoteName: "myremote", logWarning: (message, args) => warnings.Add((message, args)))); - AssertEx.Equal(new[] { string.Format(Resources.RepositoryDoesNotHaveSpecifiedRemote, repo.WorkingDirectory, "myremote", "origin") }, warnings.Select(TestUtilities.InspectDiagnostic)); - } - - /// - /// Test scenario where a local repository is cloned from another local repository that does not have remote URL specified. - /// - [Fact] - public void GetRepositoryUrl_Local_NoRemoteOriginUrl() - { - using var temp = new TempRoot(); - - var mainWorkingDir = temp.CreateDirectory(); - var mainGitDir = mainWorkingDir.CreateDirectory(".git"); - mainGitDir.CreateFile("HEAD"); - - var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] + var url = kind switch { - KVP(new GitVariableName("remote", "origin", "url"), ImmutableArray.Create(mainWorkingDir.Path)), - }))); - - var warnings = new List<(string, object?[])>(); - Assert.Equal(new Uri(mainWorkingDir.Path).AbsoluteUri, GitOperations.GetRepositoryUrl(repo, remoteName: null, logWarning: (message, args) => warnings.Add((message, args)))); - AssertEx.Equal(new[] { string.Format(Resources.RepositoryHasNoRemote, mainWorkingDir.Path) }, warnings.Select(TestUtilities.InspectDiagnostic)); - } - - /// - /// Test scenario where a local repository is cloned from another local repository that does not have a working directory. - /// - [Fact] - public void GetRepositoryUrl_Local_NoWorkingDirectory() - { - using var temp = new TempRoot(); - - var dir = temp.CreateDirectory(); - - var gitDir1 = dir.CreateDirectory("1"); - gitDir1.CreateFile("HEAD"); - gitDir1.CreateFile("config").WriteAllText(@"[remote ""origin""] url = http://github.com/repo"); - - var gitDir2 = dir.CreateDirectory("2"); - gitDir2.CreateFile("HEAD"); - gitDir2.CreateFile("config").WriteAllText(@"[remote ""origin""] url = ../1"); - - var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] - { - KVP(new GitVariableName("remote", "origin", "url"), ImmutableArray.Create(gitDir2.Path)), - }))); - - var warnings = new List<(string, object?[])>(); - Assert.Null(GitOperations.GetRepositoryUrl(repo, remoteName: null, logWarning: (message, args) => warnings.Add((message, args)))); - AssertEx.Equal(new[] { string.Format(Resources.UnableToLocateRepository, gitDir2.Path) }, warnings.Select(TestUtilities.InspectDiagnostic)); - } - - /// - /// Test scenario where a local repository is cloned from another local repository that was cloned from a remote repository. - /// - [Fact] - public void GetRepositoryUrl_Local_BadRepo() - { - using var temp = new TempRoot(); - - var mainWorkingDir = temp.CreateDirectory(); - var mainGitDir = mainWorkingDir.CreateDirectory(".git"); - - var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] - { - KVP(new GitVariableName("remote", "origin", "url"), ImmutableArray.Create(mainWorkingDir.Path)), - }))); - - var warnings = new List<(string, object?[])>(); - Assert.Equal(new Uri(mainWorkingDir.Path).AbsoluteUri, GitOperations.GetRepositoryUrl(repo, remoteName: null, logWarning: (message, args) => warnings.Add((message, args)))); - AssertEx.Equal(new[] { string.Format(Resources.RepositoryHasNoRemote, mainWorkingDir.Path) }, warnings.Select(TestUtilities.InspectDiagnostic)); - } - - [Fact] - public void GetRepositoryUrl_LocalRecursion() - { - using var temp = new TempRoot(); - - var mainWorkingDir = temp.CreateDirectory(); - var mainGitDir = mainWorkingDir.CreateDirectory(".git"); - mainGitDir.CreateFile("HEAD"); - mainGitDir.CreateFile("config").WriteAllText($@"[remote ""origin""] url = {mainWorkingDir.Path.Replace('\\', '/')}"); + "local" => originRepoPath, + "file" => new Uri(originRepoPath).AbsolutePath, + _ => kind + }; var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] { - KVP(new GitVariableName("remote", "origin", "url"), - ImmutableArray.Create(mainWorkingDir.Path)), + KVP(new GitVariableName("remote", "origin", "url"), ImmutableArray.Create(url)), }))); var warnings = new List<(string, object?[])>(); - Assert.Equal(new Uri(mainWorkingDir.Path).AbsoluteUri, GitOperations.GetRepositoryUrl(repo, remoteName: null, logWarning: (message, args) => warnings.Add((message, args)))); - AssertEx.Equal(new[] { string.Format(Resources.RepositoryUrlEvaluationExceededMaximumAllowedDepth, "10") }, warnings.Select(TestUtilities.InspectDiagnostic)); + var uri = GitOperations.GetRepositoryUrl(repo, remoteName: null, warnOnMissingOrUnsupportedRemote: true, logWarning: (message, args) => warnings.Add((message, args))); + Assert.Null(uri); + AssertEx.Equal(new[] { string.Format(Resources.InvalidRepositoryRemoteUrl, "origin", url) }, warnings.Select(TestUtilities.InspectDiagnostic)); } [Theory] @@ -468,7 +354,7 @@ public void GetSourceRoots_RepoWithCommits_WithoutUrl() commitSha: "0000000000000000000000000000000000000000"); var warnings = new List<(string, object?[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommit: true, (message, args) => warnings.Add((message, args))); + var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommitOrUnsupportedUri: true, (message, args) => warnings.Add((message, args))); AssertEx.Equal(new[] { @@ -487,7 +373,7 @@ public void GetSourceRoots_RepoWithCommits_WithUrl() ("remote.origin.url", "http://github.com/abc"))); var warnings = new List<(string, object?[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommit: true, (message, args) => warnings.Add((message, args))); + var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommitOrUnsupportedUri: true, (message, args) => warnings.Add((message, args))); AssertEx.Equal(new[] { @@ -517,7 +403,7 @@ public void GetSourceRoots_RepoWithoutCommitsWithSubmodules() CreateSubmodule("sub6", "sub/6", "", "6666666666666666666666666666666666666666"))); var warnings = new List<(string, object?[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommit: false, (message, args) => warnings.Add((message, args))); + var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommitOrUnsupportedUri: false, (message, args) => warnings.Add((message, args))); // Module without a configuration entry is not initialized. // URLs listed in .submodules are ignored (they are used by git submodule initialize to generate URLs stored in config). @@ -567,8 +453,9 @@ public void GetSourceRoots_RepoWithCommitsWithSubmodules(bool warnOnMissingCommi } } - [Fact] - public void GetSourceRoots_RelativeSubmodulePath() + [Theory] + [CombinatorialData] + public void GetSourceRoots_RelativeSubmodulePath(bool warnOnMissingCommitOrUnsupportedUri) { using var temp = new TempRoot(); @@ -586,20 +473,32 @@ public void GetSourceRoots_RelativeSubmodulePath() workingDir: repoDir.Path, commitSha: "0000000000000000000000000000000000000000", config: CreateConfig( - ("submodule.1.url", "../1")), + ("submodule.1.url", "../1"), + ("submodule.2.url", "xyz://a/b")), submodules: ImmutableArray.Create( - CreateSubmodule("1", "sub/1", "---", "1111111111111111111111111111111111111111", containingRepositoryWorkingDir: repoDir.Path))); + CreateSubmodule("1", "sub/1", "---", "1111111111111111111111111111111111111111", containingRepositoryWorkingDir: repoDir.Path), + CreateSubmodule("2", "sub/2", "---", "2222222222222222222222222222222222222222", containingRepositoryWorkingDir: repoDir.Path))); var warnings = new List<(string, object?[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommit: false, (message, args) => warnings.Add((message, args))); + var items = GitOperations.GetSourceRoots(repo, remoteName: null, warnOnMissingCommitOrUnsupportedUri, (message, args) => warnings.Add((message, args))); AssertEx.Equal(new[] { - $@"'{repoDir.Path}{s}' SourceControl='git' RevisionId='0000000000000000000000000000000000000000'", - $@"'{repoDir.Path}{s}sub{s}1{s}' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='{repoDir.Path}{s}' ScmRepositoryUrl='http://github.com/repo1'", + $@"'{repoDir.Path}{s}' SourceControl='git' RevisionId='0000000000000000000000000000000000000000'" }, items.Select(TestUtilities.InspectSourceRoot)); - Assert.Empty(warnings); + if (warnOnMissingCommitOrUnsupportedUri) + { + AssertEx.Equal(new[] + { + string.Format(Resources.SourceCodeWontBeAvailableViaSourceLink, string.Format(Resources.InvalidSubmoduleUrl, "1", "../1")), + string.Format(Resources.SourceCodeWontBeAvailableViaSourceLink, string.Format(Resources.InvalidSubmoduleUrl, "2", "xyz://a/b")) + }, warnings.Select(TestUtilities.InspectDiagnostic)); + } + else + { + Assert.Empty(warnings); + } } private static GitOperations.DirectoryNode CreateNode(string name, string? submoduleWorkingDirectory, List? children = null) diff --git a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitConfig.Reader.cs b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitConfig.Reader.cs index 3c35e8a5a..a7222efc4 100644 --- a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitConfig.Reader.cs +++ b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitConfig.Reader.cs @@ -13,7 +13,40 @@ namespace Microsoft.Build.Tasks.Git { partial class GitConfig { - internal class Reader + internal sealed class LineCountingReader(TextReader reader, string path) + { + /// + /// 1-based current line number. + /// + private int _lineNumber = 1; + + private int _last = -1; + + public int Read() + { + var c = reader.Read(); + + if (c == '\r' || c == '\n' && _last != '\r') + { + _lineNumber++; + } + + _last = c; + return c; + } + + public int Peek() + => reader.Peek(); + + public void UnexpectedCharacter() + => UnexpectedCharacter(Peek()); + + public void UnexpectedCharacter(int c) + => throw new InvalidDataException(string.Format(Resources.ErrorParsingConfigLineInFile, _lineNumber, path, + (c == -1) ? Resources.UnexpectedEndOfFile : string.Format(Resources.UnexpectedCharacter, $"U+{c:x4}"))); + } + + internal sealed class Reader { private const int MaxIncludeDepth = 10; @@ -169,11 +202,11 @@ internal void LoadVariablesFrom(string path, Dictionary? logWarning = null) - => GetRepositoryUrl(repository, remoteName, recursionDepth: 0, warnOnMissingRemote, logWarning); - - private static string? GetRepositoryUrl(GitRepository repository, string? remoteName, int recursionDepth, bool warnOnMissingRemote, Action? logWarning) + public static string? GetRepositoryUrl(GitRepository repository, string? remoteName, bool warnOnMissingOrUnsupportedRemote = true, Action? logWarning = null) { NullableDebug.Assert(repository.WorkingDirectory != null); - var remoteUrl = GetRemoteUrl(repository, ref remoteName, warnOnMissingRemote, logWarning); + var remoteUrl = GetRemoteUrl(repository, ref remoteName, warnOnMissingOrUnsupportedRemote, logWarning); if (remoteUrl == null) { return null; @@ -45,7 +40,18 @@ internal static class GitOperations return null; } - return ResolveUrl(uri, repository.Environment, remoteName, recursionDepth, warnOnMissingRemote, logWarning); + if (!IsSupportedScheme(uri.Scheme)) + { + if (warnOnMissingOrUnsupportedRemote) + { + // TODO: Better message https://github.com/dotnet/sourcelink/issues/1149 + logWarning?.Invoke(Resources.InvalidRepositoryRemoteUrl, new[] { remoteName, remoteUrl }); + } + + return null; + } + + return uri.AbsoluteUri; } private static string? GetRemoteUrl(GitRepository repository, ref string? remoteName, bool warnOnMissingRemote, Action? logWarning) @@ -79,40 +85,6 @@ internal static class GitOperations return remoteUrl; } - private static string? ResolveUrl(Uri uri, GitEnvironment environment, string? remoteName, int recursionDepth, bool warnOnMissingRemote, Action? logWarning) - { - if (!uri.IsFile) - { - return uri.AbsoluteUri; - } - - var repositoryPath = uri.LocalPath; - if (!GitRepository.TryGetRepositoryLocation(repositoryPath, out var remoteRepositoryLocation)) - { - if (warnOnMissingRemote) - { - logWarning?.Invoke(Resources.RepositoryHasNoRemote, new[] { repositoryPath }); - } - - return uri.AbsoluteUri; - } - - if (recursionDepth > RemoteRepositoryRecursionLimit) - { - logWarning?.Invoke(Resources.RepositoryUrlEvaluationExceededMaximumAllowedDepth, new[] { RemoteRepositoryRecursionLimit.ToString() }); - return null; - } - - var remoteRepository = GitRepository.OpenRepository(remoteRepositoryLocation, environment); - if (remoteRepository.WorkingDirectory == null) - { - logWarning?.Invoke(Resources.UnableToLocateRepository, new[] { repositoryPath }); - return null; - } - - return GetRepositoryUrl(remoteRepository, remoteName, recursionDepth + 1, warnOnMissingRemote, logWarning) ?? uri.AbsoluteUri; - } - private static bool TryGetRemote(GitConfig config, [NotNullWhen(true)]out string? remoteName, [NotNullWhen(true)]out string? remoteUrl) { remoteName = RemoteOriginName; @@ -177,6 +149,21 @@ internal static string ApplyInsteadOfUrlMapping(GitConfig config, string url) return NormalizeUrl(ApplyInsteadOfUrlMapping(repository.Config, url), root: repository.WorkingDirectory); } + /// + /// Git supports "http(s)", "ssh", "git" and "file" schemes. "ftp" support is obsolete. The scheme is case-sensitive. + /// See https://git-scm.com/docs/git-clone#_git_urls. + /// + /// Source Link does not support "file" scheme since repositories hosted locally or on a network share + /// are usually bare and the actual source files are not available (a work tree is not available). + /// The debugger also does not support "file" URLs in Source Link. + /// + /// The scenario of a repository on a network share that is not bare is rare since pushing into such repository + /// is not allowed by default (see configuration "receive.denyCurrentBranch") and the work tree needs + /// to be kept up to date by a service. + /// + private static bool IsSupportedScheme(string scheme) + => scheme is "http" or "https" or "ssh" or "git"; + internal static Uri? NormalizeUrl(string url, string root) { // Since git supports scp-like syntax for SSH URLs we convert it here, @@ -247,7 +234,7 @@ private static bool TryParseScp(string value, [NotNullWhen(true)]out Uri? uri) return Uri.TryCreate(url, UriKind.Absolute, out uri); } - public static ITaskItem[] GetSourceRoots(GitRepository repository, string? remoteName, bool warnOnMissingCommit, Action logWarning) + public static ITaskItem[] GetSourceRoots(GitRepository repository, string? remoteName, bool warnOnMissingCommitOrUnsupportedUri, Action logWarning) { // Not supported for repositories without a working directory. NullableDebug.Assert(repository.WorkingDirectory != null); @@ -270,7 +257,7 @@ public static ITaskItem[] GetSourceRoots(GitRepository repository, string? remot item.SetMetadata(Names.SourceRoot.RevisionId, revisionId); result.Add(item); } - else if (warnOnMissingCommit) + else if (warnOnMissingCommitOrUnsupportedUri) { logWarning(Resources.RepositoryHasNoCommit, Array.Empty()); } @@ -280,7 +267,7 @@ public static ITaskItem[] GetSourceRoots(GitRepository repository, string? remot var commitSha = submodule.HeadCommitSha; if (commitSha == null) { - if (warnOnMissingCommit) + if (warnOnMissingCommitOrUnsupportedUri) { logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink, new[] { string.Format(Resources.SubmoduleWithoutCommit, new[] { submodule.Name }) }); @@ -309,11 +296,14 @@ public static ITaskItem[] GetSourceRoots(GitRepository repository, string? remot continue; } - var submoduleUrl = ResolveUrl(submoduleUri, repository.Environment, remoteName, recursionDepth: 0, warnOnMissingRemote: true, logWarning); - if (submoduleUrl == null) + if (!IsSupportedScheme(submoduleUri.Scheme)) { - logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink, - new[] { string.Format(Resources.InvalidSubmoduleUrl, submodule.Name, submoduleConfigUrl) }); + if (warnOnMissingCommitOrUnsupportedUri) + { + // TODO: Better message https://github.com/dotnet/sourcelink/issues/1149 + logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink, + new[] { string.Format(Resources.InvalidSubmoduleUrl, submodule.Name, submoduleConfigUrl) }); + } continue; } @@ -324,7 +314,7 @@ public static ITaskItem[] GetSourceRoots(GitRepository repository, string? remot var item = new TaskItem(Evaluation.ProjectCollection.Escape(submodule.WorkingDirectoryFullPath.EndWithSeparator())); item.SetMetadata(Names.SourceRoot.SourceControl, SourceControlName); - item.SetMetadata(Names.SourceRoot.ScmRepositoryUrl, Evaluation.ProjectCollection.Escape(submoduleUrl)); + item.SetMetadata(Names.SourceRoot.ScmRepositoryUrl, Evaluation.ProjectCollection.Escape(submoduleUri.AbsoluteUri)); item.SetMetadata(Names.SourceRoot.RevisionId, commitSha); item.SetMetadata(Names.SourceRoot.ContainingRoot, Evaluation.ProjectCollection.Escape(repoRoot)); item.SetMetadata(Names.SourceRoot.NestedRoot, Evaluation.ProjectCollection.Escape(submodule.WorkingDirectoryRelativePath.EndWithSeparator('/'))); diff --git a/src/Microsoft.Build.Tasks.Git/LocateRepository.cs b/src/Microsoft.Build.Tasks.Git/LocateRepository.cs index 01384412f..2760ee3b6 100644 --- a/src/Microsoft.Build.Tasks.Git/LocateRepository.cs +++ b/src/Microsoft.Build.Tasks.Git/LocateRepository.cs @@ -53,8 +53,8 @@ private protected override void Execute(GitRepository repository) RepositoryId = repository.GitDirectory; WorkingDirectory = repository.WorkingDirectory; - Url = GitOperations.GetRepositoryUrl(repository, RemoteName, warnOnMissingRemote: !NoWarnOnMissingInfo, Log.LogWarning); - Roots = GitOperations.GetSourceRoots(repository, RemoteName, warnOnMissingCommit: !NoWarnOnMissingInfo, Log.LogWarning); + Url = GitOperations.GetRepositoryUrl(repository, RemoteName, warnOnMissingOrUnsupportedRemote: !NoWarnOnMissingInfo, Log.LogWarning); + Roots = GitOperations.GetSourceRoots(repository, RemoteName, warnOnMissingCommitOrUnsupportedUri: !NoWarnOnMissingInfo, Log.LogWarning); RevisionId = repository.GetHeadCommitSha(); } } diff --git a/src/Microsoft.Build.Tasks.Git/Resources.resx b/src/Microsoft.Build.Tasks.Git/Resources.resx index e7058ae2f..a77663540 100644 --- a/src/Microsoft.Build.Tasks.Git/Resources.resx +++ b/src/Microsoft.Build.Tasks.Git/Resources.resx @@ -198,4 +198,13 @@ Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + Unexpected end of file. + + + Unexpected character {0}. + + + Error parsing config line {0} in file '{1}': {2} + \ No newline at end of file diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.cs.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.cs.xlf index 9196789f1..fd905c9ea 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.cs.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.cs.xlf @@ -7,6 +7,11 @@ Rekurze konfiguračního souboru překročila maximální povolenou hloubku {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Chyba při čtení informací o úložišti Git: {0} @@ -117,6 +122,16 @@ Nejde najít úložiště s pracovním adresářem, který obsahuje adresář {0}. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Nepodporované rozšíření úložiště {0}. Podporují se jenom {1}. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf index c9a701e31..31d5231fe 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf @@ -7,6 +7,11 @@ Die Rekursion der Konfigurationsdatei hat die maximal zulässige Tiefe von {0} überschritten. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Fehler beim Lesen der Git-Repositoryinformationen: {0} @@ -117,6 +122,16 @@ Das Repository mit dem Arbeitsverzeichnis, welches das Verzeichnis "{0}" enthält, wurde nicht gefunden. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Nicht unterstützte Repository-Erweiterung „{0}“. Nur {1} werden unterstützt. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf index 833cadedb..89e0dba1f 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf @@ -7,6 +7,11 @@ La recursividad del archivo de configuración superó la profundidad máxima permitida de {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Error al leer la información del repositorio de Git: {0} @@ -117,6 +122,16 @@ No se puede encontrar el repositorio con el directorio de trabajo que contiene el directorio "{0}". + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Extensión de repositorio “{0}” no admitida. Solo se admite {1}. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf index 070eaab86..dbc2c6eae 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf @@ -7,6 +7,11 @@ La récursivité du fichier de configuration a dépassé la profondeur maximale autorisée de {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Erreur lors de la lecture des informations du dépôt git : {0} @@ -117,6 +122,16 @@ Impossible de localiser le dépôt avec le répertoire de travail contenant le répertoire '{0}'. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Extension de référentiel non prise en charge '{0}'. Seuls les {1} sont pris en charge. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf index 76be31100..61da07bc9 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf @@ -7,6 +7,11 @@ La ricorsione del file di configurazione ha superato la profondità massima consentita di {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Si è verificato un errore durante la lettura delle informazioni sul repository GIT: {0} @@ -117,6 +122,16 @@ Non è possibile individuare il repository con la directory di lavoro che contiene la directory '{0}'. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Estensione del repository '{0}' non supportata. Sono supportate solo {1}. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf index 733133728..58b790c14 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf @@ -7,6 +7,11 @@ 構成ファイルの再帰が、許容されている深さの上限 {0} を超えました。 + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Git リポジトリ情報の読み取りでエラーが発生しました: {0} @@ -117,6 +122,16 @@ ディレクトリ '{0}' を含む作業ディレクトリがあるリポジトリが見つかりません。 + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. サポートされていないリポジトリ拡張機能 '{0}'。{1} のみがサポートされています。 diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf index 9f33d87eb..ca7390d52 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf @@ -7,6 +7,11 @@ 구성 파일 재귀가 허용되는 최대 깊이 {0}을(를) 초과했습니다. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Git 리포지토리 정보를 읽는 동안 오류가 발생했습니다. {0} @@ -117,6 +122,16 @@ 디렉터리 '{0}'이(가) 포함된 작업 디렉터리로 리포지토리를 찾을 수 없습니다. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. 지원되지 않는 리포지토리 확장 '{0}'입니다. {1}만 지원됩니다. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf index 24fe5840d..091432504 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf @@ -7,6 +7,11 @@ Rekursja pliku konfiguracji przekroczyła maksymalną dozwoloną głębokość równą {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Błąd podczas odczytywania informacji o repozytorium git: {0} @@ -117,6 +122,16 @@ Nie można zlokalizować repozytorium z katalogiem roboczym zawierającym katalog „{0}”. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Nieobsługiwane rozszerzenie repozytorium „{0}”. Obsługiwane są tylko {1}. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pt-BR.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pt-BR.xlf index 937047cc5..8f0ecbecb 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pt-BR.xlf @@ -7,6 +7,11 @@ A recursão do arquivo de configuração excedeu a profundidade máxima permitida de {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Erro ao ler informações do repositório git: {0} @@ -117,6 +122,16 @@ Não é possível localizar o repositório com o diretório de trabalho que contém o diretório '{0}'. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Extensão de repositório sem suporte '{0}'. Somente {1} têm suporte. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf index 39a418a37..4842f3fce 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf @@ -7,6 +7,11 @@ Для рекурсии файла конфигурации превышена максимально допустимая глубина {0}. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Ошибка при чтении сведений о репозитории Git: {0} @@ -117,6 +122,16 @@ Не удается найти репозиторий с рабочим каталогом, содержащим каталог "{0}". + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. Неподдерживаемое расширение репозитория "{0}". Поддерживается только {1}. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf index f223aca36..b190c4074 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf @@ -7,6 +7,11 @@ Yapılandırma dosyası özyinelemesi izin verilen maksimum {0} derinliğini aştı. + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} Git deposu bilgileri okunurken hata: {0} @@ -117,6 +122,16 @@ '{0}' dizinini içeren çalışma dizini içeren depo bulunamıyor. + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. '{0}' depo uzantısı desteklenmiyor. Yalnızca {1} destekleniyor. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hans.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hans.xlf index 71fd1f835..17c4e9f72 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hans.xlf @@ -7,6 +7,11 @@ 配置文件递归超过了允许的最大深度 {0}。 + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} 读取 GIT 存储库信息时出错: {0} @@ -117,6 +122,16 @@ 找不到具有带目录“{0}”的工作目录的存储库。 + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. 存储库扩展“{0}”不受支持。仅支持 {1}。 diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hant.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hant.xlf index 9ef681796..f6afc7e88 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hant.xlf @@ -7,6 +7,11 @@ 組態檔遞迴已超過最大允許深度 {0}。 + + Error parsing config line {0} in file '{1}': {2} + Error parsing config line {0} in file '{1}': {2} + + Error reading git repository information: {0} 讀取 git 存放庫資訊時發生錯誤: {0} @@ -117,6 +122,16 @@ 找不到具有包含目錄 '{0}' 之工作目錄的存放庫。 + + Unexpected character {0}. + Unexpected character {0}. + + + + Unexpected end of file. + Unexpected end of file. + + Unsupported repository extension '{0}'. Only {1} are supported. 不支援的存放庫延伸模組 '{0}'。只支援 {1}。 diff --git a/src/SourceLink.Common/build/Microsoft.SourceLink.Common.props b/src/SourceLink.Common/build/Microsoft.SourceLink.Common.props index 74ccdf3a1..2b84e6838 100644 --- a/src/SourceLink.Common/build/Microsoft.SourceLink.Common.props +++ b/src/SourceLink.Common/build/Microsoft.SourceLink.Common.props @@ -7,14 +7,13 @@ - true - + true + true - + diff --git a/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets b/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets index 582ac0059..8d2e91e87 100644 --- a/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets +++ b/src/SourceLink.Common/build/Microsoft.SourceLink.Common.targets @@ -14,11 +14,14 @@ Triggers SetEmbeddedFilesFromSourceControlManagerUntrackedFiles target defined by a source control package Microsoft.Build.Tasks.{Git|Tfvc|...}. Assumes that all targets that generate source files and add them to Compile items run before BeforeCompile target. This is a convention established by common targets. + + Disabled for design-time build since calculating untracked files is non-trivial operation + and embedding them only affects the content of the generated PDB, which has no impact on design-time build. --> + Condition="'$(EmbedUntrackedSources)' == 'true' and '$(EmbedAllSources)' != 'true' and '$(DebugType)' != 'none' and '$(EnableSourceControlManagerQueries)' == 'true' and '$(DesignTimeBuild)' != 'true'" />