Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement url.<replacement>.InsteadOf #299

Merged
merged 1 commit into from Jun 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 46 additions & 3 deletions src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs
Expand Up @@ -69,6 +69,10 @@ private static GitConfig CreateConfig(params (string Name, string Value)[] varia
=> new GitConfig(ImmutableDictionary.CreateRange(
variables.Select(v => new KeyValuePair<GitVariableName, ImmutableArray<string>>(CreateVariableName(v.Name), ImmutableArray.Create(v.Value)))));

private static GitConfig CreateConfig(params (string Name, string[] Values)[] variables)
=> new GitConfig(ImmutableDictionary.CreateRange(
variables.Select(v => new KeyValuePair<GitVariableName, ImmutableArray<string>>(CreateVariableName(v.Name), ImmutableArray.CreateRange(v.Values)))));

[Fact]
public void GetRepositoryUrl_NoRemotes()
{
Expand Down Expand Up @@ -173,6 +177,20 @@ public void GetRepositoryUrl_BadUrl()
}, warnings.Select(TestUtilities.InspectDiagnostic));
}

[Fact]
public void GetRepositoryUrl_InsteadOf()
{
var repo = CreateRepository(config: new GitConfig(ImmutableDictionary.CreateRange(new[]
{
new KeyValuePair<GitVariableName, ImmutableArray<string>>(new GitVariableName("remote", "origin", "url"), ImmutableArray.Create("http://?")),
new KeyValuePair<GitVariableName, ImmutableArray<string>>(new GitVariableName("url", "git@github.com:org/repo", "insteadOf"), ImmutableArray.Create("http://?"))
})));

var warnings = new List<(string, object[])>();
Assert.Equal("ssh://git@github.com/org/repo", GitOperations.GetRepositoryUrl(repo, (message, args) => warnings.Add((message, args))));
Assert.Empty(warnings);
}

[Theory]
[InlineData("https://github.com/org/repo")]
[InlineData("http://github.com/org/repo")]
Expand Down Expand Up @@ -255,6 +273,30 @@ public void GetRepositoryUrl_ScpSyntax(string url, string expectedUrl)
Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, s_root));
}

[Theory]
[InlineData("http://test.com/test-repo", "http", "ssh", "ssh://test.com/test-repo")]
[InlineData("http://test.com/test-repo", "", "pre-", "pre-http://test.com/test-repo")]
[InlineData("http://test.com/test-repo", "http", "", "://test.com/test-repo")]
[InlineData("http://test.com/test-repo", "Http://", "xxx", "http://test.com/test-repo")]
public void ApplyInsteadOfUrlMapping_Single(string url, string prefix, string replacement, string mappedUrl)
{
var config = CreateConfig(($"url.{replacement}.insteadOf", prefix));
var actualMappedUrl = GitOperations.ApplyInsteadOfUrlMapping(config, url);
Assert.Equal(mappedUrl, actualMappedUrl);
}

[Fact]
public void ApplyInsteadOfUrlMapping_Multiple()
{
var config = CreateConfig(
("url.A.insteadOf", new[] { "http://github", "http:" }),
("url.B.insteadOf", new[] { "http://" }),
("url.C.insteadOf", new[] { "http:/" }));

var actualMappedUrl = GitOperations.ApplyInsteadOfUrlMapping(config, "http://github.com");
Assert.Equal("A.com", actualMappedUrl);
}

[Fact]
public void GetSourceRoots_RepoWithoutCommits()
{
Expand All @@ -272,17 +314,18 @@ public void GetSourceRoots_RepoWithoutCommitsWithSubmodules()
{
var repo = CreateRepository(
commitSha: null,
config: CreateConfig(("url.ssh://.insteadOf", "http://")),
submodules: ImmutableArray.Create(
CreateSubmodule("1", "sub/1", "http://1.com", "1111111111111111111111111111111111111111"),
CreateSubmodule("1", "sub/2", "http://2.com", "2222222222222222222222222222222222222222")));
CreateSubmodule("1", "sub/2", "http://2.com", "2222222222222222222222222222222222222222"))); ;

var warnings = new List<(string, object[])>();
var items = GitOperations.GetSourceRoots(repo, (message, args) => warnings.Add((message, args)));

AssertEx.Equal(new[]
{
$@"'{_workingDir}{s}sub{s}1{s}' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='http://1.com/'",
$@"'{_workingDir}{s}sub{s}2{s}' SourceControl='git' RevisionId='2222222222222222222222222222222222222222' NestedRoot='sub/2/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='http://2.com/'",
$@"'{_workingDir}{s}sub{s}1{s}' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='ssh://1.com/'",
$@"'{_workingDir}{s}sub{s}2{s}' SourceControl='git' RevisionId='2222222222222222222222222222222222222222' NestedRoot='sub/2/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='ssh://2.com/'",
}, items.Select(TestUtilities.InspectSourceRoot));

AssertEx.Equal(new[] { Resources.RepositoryHasNoCommit }, warnings.Select(TestUtilities.InspectDiagnostic));
Expand Down
37 changes: 35 additions & 2 deletions src/Microsoft.Build.Tasks.Git/GitOperations.cs
Expand Up @@ -15,6 +15,7 @@ internal static class GitOperations
{
private const string SourceControlName = "git";
private const string RemoteSectionName = "remote";
private const string UrlSectionName = "url";

public static string GetRepositoryUrl(GitRepository repository, Action<string, object[]> logWarning = null, string remoteName = null)
{
Expand All @@ -40,7 +41,7 @@ public static string GetRepositoryUrl(GitRepository repository, Action<string, o
logWarning?.Invoke(Resources.RepositoryDoesNotHaveSpecifiedRemote, new[] { unknownRemoteName, remoteName });
}

var url = NormalizeUrl(remoteUrl, repository.WorkingDirectory);
var url = NormalizeUrl(repository.Config, remoteUrl, repository.WorkingDirectory);
if (url == null)
{
logWarning?.Invoke(Resources.InvalidRepositoryRemoteUrl, new[] { remoteName, remoteUrl });
Expand Down Expand Up @@ -73,6 +74,38 @@ private static bool TryGetRemote(GitConfig config, out string remoteName, out st
return true;
}

internal static string ApplyInsteadOfUrlMapping(GitConfig config, string url)
{
// See https://git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf
// Notes:
// - URL prefix matching is case sensitive.
// - if the replacement is empty the URL is prefixed with the replacement string

int longestPrefixLength = -1;
string replacement = null;

foreach (var variable in config.Variables)
{
if (variable.Key.SectionNameEquals(UrlSectionName) &&
variable.Key.VariableNameEquals("insteadOf"))
{
foreach (var prefix in variable.Value)
{
if (prefix.Length > longestPrefixLength && url.StartsWith(prefix, StringComparison.Ordinal))
{
longestPrefixLength = prefix.Length;
replacement = variable.Key.SubsectionName;
}
}
}
}

return (longestPrefixLength >= 0) ? replacement + url.Substring(longestPrefixLength) : url;
}

internal static string NormalizeUrl(GitConfig config, string url, string root)
=> NormalizeUrl(ApplyInsteadOfUrlMapping(config, url), root);

internal static string NormalizeUrl(string url, string root)
{
// Since git supports scp-like syntax for SSH URLs we convert it here,
Expand Down Expand Up @@ -181,7 +214,7 @@ public static ITaskItem[] GetSourceRoots(GitRepository repository, Action<string
}

// https://git-scm.com/docs/git-submodule
var submoduleUrl = NormalizeUrl(submodule.Url, repoRoot);
var submoduleUrl = NormalizeUrl(repository.Config, submodule.Url, repoRoot);
if (submoduleUrl == null)
{
logWarning(Resources.SourceCodeWontBeAvailableViaSourceLink,
Expand Down