Skip to content

Commit

Permalink
Implement url.<replacement>.InsteadOf (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat committed Jun 27, 2019
1 parent 5287c59 commit a048fc2
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 5 deletions.
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

0 comments on commit a048fc2

Please sign in to comment.