diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs index d820032a..cbba9cb0 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs @@ -32,15 +32,15 @@ public class GitOperationsTests config ?? GitConfig.Empty, gitDir, gitDir, - _workingDir, + workingDir, submodules.IsDefault ? ImmutableArray.Empty : submodules, submoduleDiagnostics: ImmutableArray.Empty, ignore ?? new GitIgnore(root: null, workingDir, ignoreCase: false), commitSha); } - private GitSubmodule CreateSubmodule(string name, string relativePath, string url, string headCommitSha) - => new GitSubmodule(name, relativePath, Path.GetFullPath(Path.Combine(_workingDir, relativePath)), url, headCommitSha); + private GitSubmodule CreateSubmodule(string name, string relativePath, string url, string headCommitSha, string containingRepositoryWorkingDir = null) + => new GitSubmodule(name, relativePath, Path.GetFullPath(Path.Combine(containingRepositoryWorkingDir ?? _workingDir, relativePath)), url, headCommitSha); internal static GitIgnore CreateIgnore(string workingDirectory, string[] filePathsRelativeToWorkingDirectory) { @@ -77,7 +77,7 @@ public void GetRepositoryUrl_NoRemotes() var repo = CreateRepository(); var warnings = new List<(string, object[])>(); Assert.Null(GitOperations.GetRepositoryUrl(repo, remoteName: null, logWarning: (message, args) => warnings.Add((message, args)))); - AssertEx.Equal(new[] { Resources.RepositoryHasNoRemote }, warnings.Select(TestUtilities.InspectDiagnostic)); + AssertEx.Equal(new[] { string.Format(Resources.RepositoryHasNoRemote, repo.WorkingDirectory) }, warnings.Select(TestUtilities.InspectDiagnostic)); } [Fact] @@ -139,7 +139,7 @@ public void GetRepositoryUrl_SpecifiedNotFound_OriginFallback() AssertEx.Equal(new[] { - string.Format(Resources.RepositoryDoesNotHaveSpecifiedRemote, "myremote", "origin") + string.Format(Resources.RepositoryDoesNotHaveSpecifiedRemote, repo.WorkingDirectory, "myremote", "origin") }, warnings.Select(TestUtilities.InspectDiagnostic)); } @@ -158,7 +158,7 @@ public void GetRepositoryUrl_SpecifiedNotFound_FirstFallback() AssertEx.Equal(new[] { - string.Format(Resources.RepositoryDoesNotHaveSpecifiedRemote, "myremote", "abc") + string.Format(Resources.RepositoryDoesNotHaveSpecifiedRemote, repo.WorkingDirectory, "myremote", "abc") }, warnings.Select(TestUtilities.InspectDiagnostic)); } @@ -189,6 +189,121 @@ 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) + { + 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 repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair>(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[] + { + new KeyValuePair>(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 was cloned from a remote repository. + /// + [Fact] + public void GetRepositoryUrl_Local_NoRemote() + { + 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[] + { + new KeyValuePair>(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 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[] + { + new KeyValuePair>(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('\\', '/')}"); + + var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair>(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.RepositoryUrlEvaluationExceededMaximumAllowedDepth, "10") }, warnings.Select(TestUtilities.InspectDiagnostic)); + } + [Theory] [InlineData("https://github.com/org/repo")] [InlineData("http://github.com/org/repo")] @@ -197,19 +312,19 @@ public void GetRepositoryUrl_InsteadOf() [InlineData("abc://user@github.com/org/repo")] public void NormalizeUrl_PlatformAgnostic1(string url) { - Assert.Equal(url, GitOperations.NormalizeUrl(url, s_root)); + Assert.Equal(url, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri); } [Theory] [InlineData("http://?", null)] [InlineData("https://github.com/org/repo/./.", "https://github.com/org/repo/")] - [InlineData("http://github.com/org/\u1234", "http://github.com/org/\u1234")] + [InlineData("http://github.com/org/\u1234", "http://github.com/org/%E1%88%B4")] [InlineData("ssh://github.com/org/../repo", "ssh://github.com/repo")] [InlineData("ssh://github.com/%32/repo", "ssh://github.com/2/repo")] [InlineData("ssh://github.com/%3F/repo", "ssh://github.com/%3F/repo")] public void NormalizeUrl_PlatformAgnostic2(string url, string expectedUrl) { - Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, s_root)); + Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri); } [ConditionalTheory(typeof(WindowsOnly))] @@ -219,14 +334,17 @@ public void NormalizeUrl_PlatformAgnostic2(string url, string expectedUrl) [InlineData(@"C:x\y\..\z", null)] [InlineData(@"C:org/repo", null)] [InlineData(@"D:\src", "file:///D:/src")] + [InlineData(@"D:\a%20b", "file:///D:/a%2520b")] [InlineData(@"\\", null)] [InlineData(@"\\server", "file://server/")] [InlineData(@"\\server\dir", "file://server/dir")] [InlineData(@"relative/./path", "file:///C:/src/a/b/relative/path")] + [InlineData(@"%20", "file:///C:/src/a/b/%2520")] + [InlineData(@"..\%20", "file:///C:/src/a/%2520")] [InlineData(@"../relative/path", "file:///C:/src/a/relative/path")] [InlineData(@"..\relative\path", "file:///C:/src/a/relative/path")] [InlineData(@"../relative/path?a=b", "file:///C:/src/a/relative/path%3Fa=b")] - [InlineData(@"../relative/path*<>|\0%00", "file:///C:/src/a/relative/path*<>|/0%00")] + [InlineData(@"../relative/path*<>|\0%00", "file:///C:/src/a/relative/path*%3C%3E%7C/0%2500")] [InlineData(@"../../../../relative/path", "file:///C:/relative/path")] [InlineData(@"a:/../../relative/path", "file:///a:/relative/path")] [InlineData(@"Z:/a/b/../../relative/path", "file:///Z:/relative/path")] @@ -237,17 +355,20 @@ public void NormalizeUrl_PlatformAgnostic2(string url, string expectedUrl) [InlineData(@"@:org/repo", "file:///C:/src/a/b/@:org/repo")] public void NormalizeUrl_Windows(string url, string expectedUrl) { - Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, @"C:\src\a\b")); + Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, @"C:\src\a\b")?.AbsoluteUri); } [ConditionalTheory(typeof(UnixOnly))] [InlineData(@"C:org/repo", @"ssh://c/org/repo")] [InlineData(@"/xyz/src", @"file:///xyz/src")] + // [InlineData(@"/a%20b", @"file:///a%2520b")] // https://github.com/dotnet/sourcelink/issues/439 [InlineData(@"\path\a\b", @"file:///path/a/b")] [InlineData(@"relative/./path", @"file:///usr/src/a/b/relative/path")] + [InlineData(@"%20", "file:///usr/src/a/b/%2520")] + [InlineData(@"../%20", "file:///usr/src/a/%2520")] [InlineData(@"../relative/path", @"file:///usr/src/a/relative/path")] [InlineData(@"../relative/path?a=b", @"file:///usr/src/a/relative/path%3Fa=b")] - [InlineData(@"../relative/path*<>|\0%00", @"file:///usr/src/a/relative/path*<>|\0%00")] + // [InlineData(@"../relative/path*<>|\0%00", @"file:///usr/src/a/relative/path*%3C%3E%7C/0%2500")] // https://github.com/dotnet/sourcelink/issues/439 [InlineData(@"../../../../relative/path", @"file:///relative/path")] [InlineData(@"../.://../../relative/path", "file:///usr/src/a/relative/path")] [InlineData(@"../.:./../../relative/path", "ssh://../relative/path")] @@ -256,11 +377,12 @@ public void NormalizeUrl_Windows(string url, string expectedUrl) [InlineData(@"@:org/repo", @"file:///usr/src/a/b/@:org/repo")] public void NormalizeUrl_Unix(string url, string expectedUrl) { - Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, "/usr/src/a/b")); + Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, "/usr/src/a/b")?.AbsoluteUri); } [Theory] [InlineData("abc:org/repo", "ssh://abc/org/repo")] + [InlineData("abc:org/x%20y", "ssh://abc/org/x%20y")] [InlineData("ABC:ORG/REPO/X/Y", "ssh://abc/ORG/REPO/X/Y")] [InlineData("github.com:org/repo", "ssh://github.com/org/repo")] [InlineData("git@github.com:org/repo", "ssh://git@github.com/org/repo")] @@ -268,7 +390,7 @@ public void NormalizeUrl_Unix(string url, string expectedUrl) [InlineData("http:x//y", "ssh://http/x//y")] public void GetRepositoryUrl_ScpSyntax(string url, string expectedUrl) { - Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, s_root)); + Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri); } [Theory] @@ -351,97 +473,34 @@ public void GetSourceRoots_RepoWithCommitsWithSubmodules() warnings.Select(TestUtilities.InspectDiagnostic)); } - [ConditionalFact(typeof(WindowsOnly))] - public void GetSourceRoots_RelativeSubmodulePaths_Windows() - { - _workingDir = @"C:\src"; - - var repo = CreateRepository( - commitSha: "0000000000000000000000000000000000000000", - submodules: ImmutableArray.Create( - CreateSubmodule("1", "sub/1", "./a/b", "1111111111111111111111111111111111111111"), - CreateSubmodule("2", "sub/2", "../a", "2222222222222222222222222222222222222222"))); - - var warnings = new List<(string, object[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, (message, args) => warnings.Add((message, args))); - - AssertEx.Equal(new[] - { - $@"'C:\src\' SourceControl='git' RevisionId='0000000000000000000000000000000000000000'", - $@"'C:\src\sub\1\' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='C:\src\' ScmRepositoryUrl='file:///C:/src/a/b'", - $@"'C:\src\sub\2\' SourceControl='git' RevisionId='2222222222222222222222222222222222222222' NestedRoot='sub/2/' ContainingRoot='C:\src\' ScmRepositoryUrl='file:///C:/a'", - }, items.Select(TestUtilities.InspectSourceRoot)); - - Assert.Empty(warnings); - } - - [ConditionalFact(typeof(WindowsOnly))] - public void GetSourceRoots_RelativeSubmodulePaths_Windows_UnicodeAndEscapes() - { - _workingDir = @"C:\%25@噸"; - - var repo = CreateRepository( - commitSha: "0000000000000000000000000000000000000000", - submodules: ImmutableArray.Create( - CreateSubmodule("%25ሴ", "sub/%25ሴ", "./a/b", "1111111111111111111111111111111111111111"), - CreateSubmodule("%25ለ", "sub/%25ለ", "../a", "2222222222222222222222222222222222222222"))); - - var warnings = new List<(string, object[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, (message, args) => warnings.Add((message, args))); - - AssertEx.Equal(new[] - { - $@"'C:\%25@噸\' SourceControl='git' RevisionId='0000000000000000000000000000000000000000'", - $@"'C:\%25@噸\sub\%25ሴ\' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/%25ሴ/' ContainingRoot='C:\%25@噸\' ScmRepositoryUrl='file:///C:/%25@噸/a/b'", - $@"'C:\%25@噸\sub\%25ለ\' SourceControl='git' RevisionId='2222222222222222222222222222222222222222' NestedRoot='sub/%25ለ/' ContainingRoot='C:\%25@噸\' ScmRepositoryUrl='file:///C:/a'", - }, items.Select(TestUtilities.InspectSourceRoot)); - - Assert.Empty(warnings); - } - - [ConditionalFact(typeof(UnixOnly))] - public void GetSourceRoots_RelativeSubmodulePaths_Unix() + [Fact] + public void GetSourceRoots_RelativeSubmodulePaths() { - _workingDir = @"/src"; + using var temp = new TempRoot(); - var repo = CreateRepository( - commitSha: "0000000000000000000000000000000000000000", - submodules: ImmutableArray.Create( - CreateSubmodule("1", "sub/1", "./a/b", "1111111111111111111111111111111111111111"), - CreateSubmodule("2", "sub/2", "../a", "2222222222222222222222222222222222222222"))); - - var warnings = new List<(string, object[])>(); - var items = GitOperations.GetSourceRoots(repo, remoteName: null, (message, args) => warnings.Add((message, args))); - - AssertEx.Equal(new[] - { - $@"'/src/' SourceControl='git' RevisionId='0000000000000000000000000000000000000000'", - $@"'/src/sub/1/' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='/src/' ScmRepositoryUrl='file:///src/a/b'", - $@"'/src/sub/2/' SourceControl='git' RevisionId='2222222222222222222222222222222222222222' NestedRoot='sub/2/' ContainingRoot='/src/' ScmRepositoryUrl='file:///a'", - }, items.Select(TestUtilities.InspectSourceRoot)); + var dir = temp.CreateDirectory(); - Assert.Empty(warnings); - } + // TODO: test unicode chars on Linux as well https://github.com/dotnet/corefx/issues/34227 + var repoDir = dir.CreateDirectory("%25@" + (s == '\\' ? "噸" : "")); - [ConditionalFact(typeof(UnixOnly), Skip = "https://github.com/dotnet/corefx/issues/34227")] - public void GetSourceRoots_RelativeSubmodulePaths_Unix_UnicodeAndEscapes() - { - _workingDir = @"/%25@噸"; + var repo1WorkingDir = dir.CreateDirectory("1"); + var repo1GitDir = repo1WorkingDir.CreateDirectory(".git"); + repo1GitDir.CreateFile("HEAD"); + repo1GitDir.CreateFile("config").WriteAllText(@"[remote ""origin""] url = http://github.com/repo1"); var repo = CreateRepository( + workingDir: repoDir.Path, commitSha: "0000000000000000000000000000000000000000", submodules: ImmutableArray.Create( - CreateSubmodule("%25ሴ", "sub/%25ሴ", "./a/b", "1111111111111111111111111111111111111111"), - CreateSubmodule("%25ለ", "sub/%25ለ", "../a", "2222222222222222222222222222222222222222"))); + CreateSubmodule("1", "sub/1", "../1", "1111111111111111111111111111111111111111", containingRepositoryWorkingDir: repoDir.Path))); var warnings = new List<(string, object[])>(); var items = GitOperations.GetSourceRoots(repo, remoteName: null, (message, args) => warnings.Add((message, args))); AssertEx.Equal(new[] { - $@"'/%25@噸/' SourceControl='git' RevisionId='0000000000000000000000000000000000000000'", - $@"'/%25@噸/sub/%25ሴ/' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/%25ሴ/' ContainingRoot='/%25@噸/' ScmRepositoryUrl='file:///%25@噸/a/b'", - $@"'/%25@噸/sub/%25ለ/' SourceControl='git' RevisionId='2222222222222222222222222222222222222222' NestedRoot='sub/%25ለ/' ContainingRoot='/%25@噸/' ScmRepositoryUrl='file:///a'", + $@"'{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'", }, items.Select(TestUtilities.InspectSourceRoot)); Assert.Empty(warnings); diff --git a/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs b/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs index 2bf7add7..2d650e00 100644 --- a/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs +++ b/src/Microsoft.Build.Tasks.Git.UnitTests/GitRepositoryTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Build.Tasks.Git.UnitTests public class GitRepositoryTests { [Fact] - public void LocateRepository_Worktree() + public void TryFindRepository_Worktree() { using var temp = new TempRoot(); @@ -30,48 +30,32 @@ public void LocateRepository_Worktree() worktreeGitDir.CreateFile("gitdir").WriteAllText(worktreeGitFile.Path + " \r\n\t\v"); // start under main repository directory: - Assert.True(GitRepository.LocateRepository( - mainWorkingSubDir.Path, - out var locatedGitDirectory, - out var locatedCommonDirectory, - out var locatedWorkingDirectory)); + Assert.True(GitRepository.TryFindRepository(mainWorkingSubDir.Path, out var location)); - Assert.Equal(mainGitDir.Path, locatedGitDirectory); - Assert.Equal(mainGitDir.Path, locatedCommonDirectory); - Assert.Equal(mainWorkingDir.Path, locatedWorkingDirectory); + Assert.Equal(mainGitDir.Path, location.GitDirectory); + Assert.Equal(mainGitDir.Path, location.CommonDirectory); + Assert.Equal(mainWorkingDir.Path, location.WorkingDirectory); // start at main git directory (git config works from this dir, but git status requires work dir): - Assert.True(GitRepository.LocateRepository( - mainGitDir.Path, - out locatedGitDirectory, - out locatedCommonDirectory, - out locatedWorkingDirectory)); + Assert.True(GitRepository.TryFindRepository(mainGitDir.Path, out location)); - Assert.Equal(mainGitDir.Path, locatedGitDirectory); - Assert.Equal(mainGitDir.Path, locatedCommonDirectory); - Assert.Null(locatedWorkingDirectory); + Assert.Equal(mainGitDir.Path, location.GitDirectory); + Assert.Equal(mainGitDir.Path, location.CommonDirectory); + Assert.Null(location.WorkingDirectory); // start under worktree directory: - Assert.True(GitRepository.LocateRepository( - worktreeSubDir.Path, - out locatedGitDirectory, - out locatedCommonDirectory, - out locatedWorkingDirectory)); + Assert.True(GitRepository.TryFindRepository(worktreeSubDir.Path, out location)); - Assert.Equal(worktreeGitDir.Path, locatedGitDirectory); - Assert.Equal(mainGitDir.Path, locatedCommonDirectory); - Assert.Equal(worktreeDir.Path, locatedWorkingDirectory); + Assert.Equal(worktreeGitDir.Path, location.GitDirectory); + Assert.Equal(mainGitDir.Path, location.CommonDirectory); + Assert.Equal(worktreeDir.Path, location.WorkingDirectory); // start under worktree git directory (git config works from this dir, but git status requires work dir): - Assert.True(GitRepository.LocateRepository( - worktreeGitSubDir.Path, - out locatedGitDirectory, - out locatedCommonDirectory, - out locatedWorkingDirectory)); - - Assert.Equal(worktreeGitDir.Path, locatedGitDirectory); - Assert.Equal(mainGitDir.Path, locatedCommonDirectory); - Assert.Null(locatedWorkingDirectory); + Assert.True(GitRepository.TryFindRepository(worktreeGitSubDir.Path, out location)); + + Assert.Equal(worktreeGitDir.Path, location.GitDirectory); + Assert.Equal(mainGitDir.Path, location.CommonDirectory); + Assert.Null(location.WorkingDirectory); } [Fact] @@ -93,26 +77,18 @@ public void LocateRepository_Submodule() submoduleGitDir.CreateDirectory("refs"); // start under submodule working directory: - Assert.True(GitRepository.LocateRepository( - submoduleWorkDir.Path, - out var locatedGitDirectory, - out var locatedCommonDirectory, - out var locatedWorkingDirectory)); + Assert.True(GitRepository.TryFindRepository(submoduleWorkDir.Path, out var location)); - Assert.Equal(submoduleGitDir.Path, locatedGitDirectory); - Assert.Equal(submoduleGitDir.Path, locatedCommonDirectory); - Assert.Equal(submoduleWorkDir.Path, locatedWorkingDirectory); + Assert.Equal(submoduleGitDir.Path, location.GitDirectory); + Assert.Equal(submoduleGitDir.Path, location.CommonDirectory); + Assert.Equal(submoduleWorkDir.Path, location.WorkingDirectory); // start under submodule git directory: - Assert.True(GitRepository.LocateRepository( - submoduleGitDir.Path, - out locatedGitDirectory, - out locatedCommonDirectory, - out locatedWorkingDirectory)); - - Assert.Equal(submoduleGitDir.Path, locatedGitDirectory); - Assert.Equal(submoduleGitDir.Path, locatedCommonDirectory); - Assert.Null(locatedWorkingDirectory); + Assert.True(GitRepository.TryFindRepository(submoduleGitDir.Path, out location)); + + Assert.Equal(submoduleGitDir.Path, location.GitDirectory); + Assert.Equal(submoduleGitDir.Path, location.CommonDirectory); + Assert.Null(location.WorkingDirectory); } [Fact] diff --git a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs index f1ec1d4d..a06a5bcc 100644 --- a/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs +++ b/src/Microsoft.Build.Tasks.Git/GitDataReader/GitRepository.cs @@ -85,27 +85,7 @@ internal GitRepository(GitEnvironment environment, GitConfig config, string gitD _lazySubmodules = new Lazy<(ImmutableArray, ImmutableArray)>(() => (submodules, submoduleDiagnostics)); _lazyIgnore = new Lazy(() => ignore); _lazyHeadCommitSha = new Lazy(() => headCommitSha); - } - - /// - /// Finds a git repository containing the specified path, if any. - /// - /// - /// - /// The repository found requires higher version of git repository format that is currently supported. - /// False if no git repository can be found that contains the specified path. - public static bool TryFindRepository(string path, out GitRepositoryLocation location) - { - if (!LocateRepository(path, out var gitDirectory, out var commonDirectory, out var defaultWorkingDirectory)) - { - // unable to find repository - location = default; - return false; - } - - location = new GitRepositoryLocation(gitDirectory, commonDirectory, defaultWorkingDirectory); - return true; - } + } /// /// Opens a repository at the specified location. @@ -237,7 +217,7 @@ internal string ReadSubmoduleHeadCommitSha(string submoduleWorkingDirectoryFullP var resolver = new GitReferenceResolver(gitDirectory, commonDirectory); return resolver.ResolveHeadReference(); - } + } private string GetWorkingDirectory() => WorkingDirectory ?? throw new InvalidOperationException(Resources.RepositoryDoesNotHaveWorkingDirectory); @@ -368,63 +348,100 @@ private GitIgnore LoadIgnore() return new GitIgnore(root, workingDirectory, ignoreCase); } + /// + /// Returns if the specified is + /// a valid repository directory. + /// /// /// - internal static bool LocateRepository(string directory, out string gitDirectory, out string commonDirectory, out string workingDirectory) + /// The repository found requires higher version of git repository format that is currently supported. + /// False if no git repository can be found that contains the specified path. + public static bool TryGetRepositoryLocation(string directory, out GitRepositoryLocation location) { - gitDirectory = commonDirectory = workingDirectory = null; + try + { + directory = Path.GetFullPath(directory); + } + catch + { + location = default; + return false; + } + return TryGetRepositoryLocationImpl(directory, out location); + } + + /// + /// Finds a git repository containing the specified path, if any. + /// + /// + /// + /// The repository found requires higher version of git repository format that is currently supported. + /// False if no git repository can be found that contains the specified path. + public static bool TryFindRepository(string directory, out GitRepositoryLocation location) + { try { directory = Path.GetFullPath(directory); } catch { + location = default; return false; } while (directory != null) { + if (TryGetRepositoryLocationImpl(directory, out location)) + { + return true; + } + // TODO: https://github.com/dotnet/sourcelink/issues/302 // stop on device boundary + directory = Path.GetDirectoryName(directory); + } + + location = default; + return false; + } - var dotGitPath = Path.Combine(directory, GitDirName); + private static bool TryGetRepositoryLocationImpl(string directory, out GitRepositoryLocation location) + { + string commonDirectory; + var dotGitPath = Path.Combine(directory, GitDirName); - if (Directory.Exists(dotGitPath)) + if (Directory.Exists(dotGitPath)) + { + if (IsGitDirectory(dotGitPath, out commonDirectory)) { - if (IsGitDirectory(dotGitPath, out commonDirectory)) - { - gitDirectory = dotGitPath; - workingDirectory = directory; - return true; - } + location = new GitRepositoryLocation(gitDirectory: dotGitPath, commonDirectory, workingDirectory: directory); + return true; } - else if (File.Exists(dotGitPath)) + } + else if (File.Exists(dotGitPath)) + { + var link = ReadDotGitFile(dotGitPath); + if (IsGitDirectory(link, out commonDirectory)) { - var link = ReadDotGitFile(dotGitPath); - if (IsGitDirectory(link, out commonDirectory)) - { - gitDirectory = link; - workingDirectory = directory; - return true; - } - - return false; + location = new GitRepositoryLocation(gitDirectory: link, commonDirectory, workingDirectory: directory); + return true; } - if (Directory.Exists(directory)) + location = default; + return false; + } + + if (Directory.Exists(directory)) + { + if (IsGitDirectory(directory, out commonDirectory)) { - if (IsGitDirectory(directory, out commonDirectory)) - { - gitDirectory = directory; - workingDirectory = null; - return true; - } + location = new GitRepositoryLocation(gitDirectory: directory, commonDirectory, workingDirectory: null); + return true; } - - directory = Path.GetDirectoryName(directory); } + location = default; return false; } diff --git a/src/Microsoft.Build.Tasks.Git/GitOperations.cs b/src/Microsoft.Build.Tasks.Git/GitOperations.cs index 4bb58dba..be7f3139 100644 --- a/src/Microsoft.Build.Tasks.Git/GitOperations.cs +++ b/src/Microsoft.Build.Tasks.Git/GitOperations.cs @@ -13,6 +13,8 @@ namespace Microsoft.Build.Tasks.Git { internal static class GitOperations { + private const int RemoteRepositoryRecursionLimit = 10; + private const string SourceControlName = "git"; private const string RemoteSectionName = "remote"; private const string RemoteOriginName = "origin"; @@ -20,6 +22,27 @@ internal static class GitOperations private const string UrlVariableName = "url"; public static string GetRepositoryUrl(GitRepository repository, string remoteName, Action logWarning = null) + => GetRepositoryUrl(repository, remoteName, recursionDepth: 0, logWarning); + + private static string GetRepositoryUrl(GitRepository repository, string remoteName, int recursionDepth, Action logWarning = null) + { + var remoteUrl = GetRemoteUrl(repository, ref remoteName, logWarning); + if (remoteUrl == null) + { + return null; + } + + var uri = NormalizeUrl(repository.Config, remoteUrl, repository.WorkingDirectory); + if (uri == null) + { + logWarning?.Invoke(Resources.InvalidRepositoryRemoteUrl, new[] { remoteName, remoteUrl }); + return null; + } + + return ResolveUrl(uri, repository.Environment, remoteName, recursionDepth, logWarning); + } + + private static string GetRemoteUrl(GitRepository repository, ref string remoteName, Action logWarning) { string unknownRemoteName = null; string remoteUrl = null; @@ -34,22 +57,40 @@ public static string GetRepositoryUrl(GitRepository repository, string remoteNam if (remoteUrl == null && !TryGetRemote(repository.Config, out remoteName, out remoteUrl)) { - logWarning?.Invoke(Resources.RepositoryHasNoRemote, Array.Empty()); + logWarning?.Invoke(Resources.RepositoryHasNoRemote, new[] { repository.WorkingDirectory }); return null; } if (unknownRemoteName != null) { - logWarning?.Invoke(Resources.RepositoryDoesNotHaveSpecifiedRemote, new[] { unknownRemoteName, remoteName }); + logWarning?.Invoke(Resources.RepositoryDoesNotHaveSpecifiedRemote, new[] { repository.WorkingDirectory, unknownRemoteName, remoteName }); } - var url = NormalizeUrl(repository.Config, remoteUrl, repository.WorkingDirectory); - if (url == null) + return remoteUrl; + } + + private static string ResolveUrl(Uri uri, GitEnvironment environment, string remoteName, int recursionDepth, Action logWarning) + { + if (!uri.IsFile) { - logWarning?.Invoke(Resources.InvalidRepositoryRemoteUrl, new[] { remoteName, remoteUrl }); + return uri.AbsoluteUri; + } + + var repositoryPath = uri.LocalPath; + if (!GitRepository.TryGetRepositoryLocation(repositoryPath, out var remoteRepositoryLocation)) + { + logWarning?.Invoke(Resources.RepositoryHasNoRemote, new[] { repositoryPath }); + return uri.AbsoluteUri; + } + + if (recursionDepth > RemoteRepositoryRecursionLimit) + { + logWarning?.Invoke(Resources.RepositoryUrlEvaluationExceededMaximumAllowedDepth, new[] { RemoteRepositoryRecursionLimit.ToString() }); + return null; } - return url; + var remoteRepository = GitRepository.OpenRepository(remoteRepositoryLocation, environment); + return GetRepositoryUrl(remoteRepository, remoteName, recursionDepth + 1, logWarning) ?? uri.AbsoluteUri; } private static bool TryGetRemote(GitConfig config, out string remoteName, out string remoteUrl) @@ -105,10 +146,10 @@ internal static string ApplyInsteadOfUrlMapping(GitConfig config, string url) return (longestPrefixLength >= 0) ? replacement + url.Substring(longestPrefixLength) : url; } - internal static string NormalizeUrl(GitConfig config, string url, string root) + internal static Uri NormalizeUrl(GitConfig config, string url, string root) => NormalizeUrl(ApplyInsteadOfUrlMapping(config, url), root); - internal static string NormalizeUrl(string url, string root) + internal static Uri NormalizeUrl(string url, string root) { // Since git supports scp-like syntax for SSH URLs we convert it here, // so that RepositoryUrl is actually a valid URL in that case. @@ -117,12 +158,12 @@ internal static string NormalizeUrl(string url, string root) // Windows device path "X:" if (url.Length == 2 && IsWindowsAbsoluteOrDriveRelativePath(url)) { - return "file:///" + url + "/"; + return new Uri("file:///" + url + "/"); } if (TryParseScp(url, out var uri)) { - return uri.ToString(); + return uri; } if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri)) @@ -132,14 +173,14 @@ internal static string NormalizeUrl(string url, string root) if (uri.IsAbsoluteUri) { - return uri.ToString(); + return uri; } // Convert relative local path to absolute: var rootUri = new Uri(root.EndWithSeparator('/')); if (Uri.TryCreate(rootUri, uri, out uri)) { - return uri.ToString(); + return uri; } return null; @@ -215,15 +256,24 @@ public static ITaskItem[] GetSourceRoots(GitRepository repository, string remote } // https://git-scm.com/docs/git-submodule - var submoduleUrl = NormalizeUrl(repository.Config, submodule.Url, repoRoot); - if (submoduleUrl == null) + var submoduleUri = NormalizeUrl(repository.Config, submodule.Url, repoRoot); + if (submoduleUri == null) { - logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink, + logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink, new[] { string.Format(Resources.InvalidSubmoduleUrl, submodule.Name, submodule.Url) }); continue; } + var submoduleUrl = ResolveUrl(submoduleUri, repository.Environment, remoteName, recursionDepth: 0, logWarning); + if (submoduleUrl == null) + { + logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink, + new[] { string.Format(Resources.InvalidSubmoduleUrl, submodule.Name, submodule.Url) }); + + continue; + } + // Item metadata are stored msbuild-escaped. GetMetadata unescapes, SetMetadata stores the value as specified. // Escape msbuild special characters so that URL escapes and non-ascii characters in the URL and paths are // preserved when read by GetMetadata. diff --git a/src/Microsoft.Build.Tasks.Git/Resources.resx b/src/Microsoft.Build.Tasks.Git/Resources.resx index 5821d65d..76d7b01e 100644 --- a/src/Microsoft.Build.Tasks.Git/Resources.resx +++ b/src/Microsoft.Build.Tasks.Git/Resources.resx @@ -175,7 +175,7 @@ Repository has no commit. - Repository has no remote. + Repository '{0}' has no remote. The URL of repository remote '{0}' is invalid: '{1}'. @@ -184,7 +184,7 @@ Unable to locate repository with working directory that contains directory '{0}'. - Repository does not have the specified remote '{0}'; using '{1}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. The value of {0} is not a valid configuration scope: '{1}'. @@ -192,4 +192,7 @@ Home relative paths are not allowed when {0} is '{1}': '{2}'. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + \ 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 1cd339a4..83a89752 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.cs.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.cs.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Úložiště nemá zadané vzdálené {0}. Místo toho se použije {1}. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Úložiště nemá žádnou vzdálenou entitu. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ Úložiště nemá žádné potvrzení. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} Zdrojový kód nebude k dispozici přes Source Link. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf index 57156182..95fb297f 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.de.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Das Repository verfügt nicht über die angegebene Remoteinstanz "{0}". Stattdessen wird "{1}" verwendet. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Es besteht keine Remotverbindung mit dem Repository. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ Kein Commit für Repository vorhanden. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0}: Der Quellcode ist nicht über SourceLink verfügbar. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf index 2050cc08..2b20874f 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.es.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - El repositorio no tiene el remoto especificado "{0}"; se usará "{1}" en su lugar. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - El repositorio no tiene remoto. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ El repositorio no tiene commit. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} El código fuente no estará disponible a través del vínculo de origen. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf index 9533088a..8576be39 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.fr.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Le dépôt n'a pas le dépôt distant '{0}' spécifié ; utilisation de '{1}' à la place. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Le dépôt n'a aucun remote (lien à distance). + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ Le dépôt n'a aucune validation. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} Le code source n'est pas disponible via Source Link. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf index 5b821933..2bb7ad53 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.it.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Il repository remoto '{0}' specificato non è presente nel repository. Verrà usato '{1}'. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Non esiste alcun ramo remoto per il repository. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ Non esiste alcun commit per il repository. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} Il codice sorgente non sarà disponibile tramite il collegamento all'origine. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf index ddb9cb9b..94ec882c 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ja.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - リポジトリには、指定されたリモート '{0}' がありません。代わりに '{1}' が使用されています。 + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - リモート リポジトリがありません。 + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ リポジトリにコミットがありません。 + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} ソース リンク経由ではソース コードを使用できません。 diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf index 001591e9..cf4622c4 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ko.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - 리포지토리에 지정한 원격 '{0}'이 (가) 없습니다. 대신 '{1}'을 사용합니다. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - 리포지토리에 원격이 없습니다. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ 저장소에 커밋 내역이 없습니다. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} 소스 링크를 통해 소스 코드를 사용할 수 없습니다. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf index 953f8c41..05551fcb 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pl.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Repozytorium nie ma określonego repozytorium zdalnego „{0}”; w zastępstwie używane jest repozytorium „{1}”. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Repozytorium ma nie lokalizacji zdalnej. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ W repozytorium nie ma zatwierdzenia. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} Kod źródłowy nie będzie dostępny za pośrednictwem linku źródłowego. 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 0a5de6ae..e590c7e1 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.pt-BR.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - O repositório não tem o '{0}' remoto especificado; em vez disso, está usando '{1}'. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - O repositório não tem controle remoto. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ O repositório não tem nenhuma confirmação. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} O código-fonte não estará disponível por meio do Link de Origem. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf index 6b2db2ce..d6a81f5e 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.ru.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Репозиторий не содержит указанный удаленный каталог "{0}"; вместо него будет использован каталог "{1}". + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Отсутствует удаленный репозиторий для этого репозитория. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ Хранилище не содержит фиксаций. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} Исходный код не будет доступен по ссылке. diff --git a/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf b/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf index bace5e8f..cf080d25 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.tr.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - Depo, belirtilen '{0}' uzak öğesine sahip değil; bunun yerine '{1}' kullanılıyor. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - Depoda uzak kaynak yok. + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ Depoda işleme yok. + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} Kaynak kodu, Kaynak Bağlantısı ile kullanılmaz. 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 d5a2e57e..488811ac 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hans.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - 存储库不具有指定的远程“{0}”;请改用“{1}”。 + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - 存储库没有远程。 + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ 存储库没有提交。 + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} 无法通过源链接获取源代码。 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 63085765..aac1b154 100644 --- a/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.Build.Tasks.Git/xlf/Resources.zh-Hant.xlf @@ -78,8 +78,8 @@ - Repository does not have the specified remote '{0}'; using '{1}' instead. - 存放庫沒有指定的遠端 '{0}'; 請改用 '{1}'。 + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. + Repository '{0}' does not have the specified remote '{1}'; using '{2}' instead. @@ -88,8 +88,8 @@ - Repository has no remote. - 存放庫沒有遠端。 + Repository '{0}' has no remote. + Repository '{0}' has no remote. @@ -97,6 +97,11 @@ 存放庫沒有認可。 + + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + Repository URL evaluation exceeded maximum allowed depth of {0} local repositories. + + {0} The source code won't be available via Source Link. {0} 無法透過來源連結使用原始程式碼。