Skip to content

Commit

Permalink
Feature: GitWeb Source Link provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Glen-Nicol-Garmin committed Nov 25, 2019
1 parent b02ee10 commit e1b2261
Show file tree
Hide file tree
Showing 23 changed files with 710 additions and 0 deletions.
12 changes: 12 additions & 0 deletions SourceLink.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SourceLink.AzureD
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SourceLink.AzureDevOpsServer.Git.UnitTests", "src\SourceLink.AzureDevOpsServer.Git.UnitTests\Microsoft.SourceLink.AzureDevOpsServer.Git.UnitTests.csproj", "{79371F26-FB84-408D-A4A1-B142B247C288}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SourceLink.GitWeb", "src\SourceLink.GitWeb\Microsoft.SourceLink.GitWeb.csproj", "{C78DD3EF-9D20-4E00-8237-E871BB53F840}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SourceLink.GitWeb.UnitTests", "src\SourceLink.GitWeb.UnitTests\Microsoft.SourceLink.GitWeb.UnitTests.csproj", "{50503A43-08C0-493B-B8CC-F368983644C1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -135,6 +139,14 @@ Global
{79371F26-FB84-408D-A4A1-B142B247C288}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79371F26-FB84-408D-A4A1-B142B247C288}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79371F26-FB84-408D-A4A1-B142B247C288}.Release|Any CPU.Build.0 = Release|Any CPU
{C78DD3EF-9D20-4E00-8237-E871BB53F840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C78DD3EF-9D20-4E00-8237-E871BB53F840}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C78DD3EF-9D20-4E00-8237-E871BB53F840}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C78DD3EF-9D20-4E00-8237-E871BB53F840}.Release|Any CPU.Build.0 = Release|Any CPU
{50503A43-08C0-493B-B8CC-F368983644C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50503A43-08C0-493B-B8CC-F368983644C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50503A43-08C0-493B-B8CC-F368983644C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50503A43-08C0-493B-B8CC-F368983644C1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
58 changes: 58 additions & 0 deletions src/SourceLink.GitWeb.UnitTests/GetSourceLinkUrlTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See
// License.txt in the project root for license information.
using Microsoft.Build.Tasks.SourceControl;
using TestUtilities;
using Xunit;
using static TestUtilities.KeyValuePairUtils;

namespace Microsoft.SourceLink.GitWeb.UnitTests
{
public class GetSourceLinkUrlTests
{
[Fact]
public void EmptyHosts()
{
var engine = new MockEngine();

var task = new GetSourceLinkUrl()
{
BuildEngine = engine,
SourceRoot = new MockItem("x", KVP("RepositoryUrl", "http://abc.com"), KVP("SourceControl", "git")),
};

bool result = task.Execute();

AssertEx.AssertEqualToleratingWhitespaceDifferences(
"ERROR : " + string.Format(CommonResources.AtLeastOneRepositoryHostIsRequired, "SourceLinkGitWebHost", "GitWeb"), engine.Log);

Assert.False(result);
}

[Theory]
[InlineData("", "")]
[InlineData("", "/")]
[InlineData("/", "")]
[InlineData("/", "/")]
public void BuildSourceLinkUrl(string s1, string s2)
{
var engine = new MockEngine();

var task = new GetSourceLinkUrl()
{
BuildEngine = engine,
SourceRoot = new MockItem("/src/", KVP("RepositoryUrl", "ssh://git@src.intranet.company.com/root_dir_name/sub_dirs/reponame.git" + s1), KVP("SourceControl", "git"), KVP("RevisionId", "0123456789abcdefABCDEF000000000000000000")),
Hosts = new[]
{
// NOTE: i don't know what the spec parameter is for. but for all the other
// tests like this for various providers it is the domain of the Repo URL.
new MockItem("src.intranet.company.com", KVP("ContentUrl", "https://src.intranet.company.com/gitweb" + s2)),
}
};

bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);
AssertEx.AreEqual("https://src.intranet.company.com/gitweb/?p=root_dir_name/sub_dirs/reponame.git;a=blob_plain;hb=0123456789abcdefABCDEF000000000000000000;f=*", task.SourceLinkUrl);
Assert.True(result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SourceLink.GitWeb\Microsoft.SourceLink.GitWeb.csproj" />
<ProjectReference Include="..\TestUtilities\TestUtilities.csproj" />
</ItemGroup>
</Project>
51 changes: 51 additions & 0 deletions src/SourceLink.GitWeb.UnitTests/TranslateRepositoryUrlsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See
// License.txt in the project root for license information.
using System.Linq;
using TestUtilities;
using Xunit;
using static TestUtilities.KeyValuePairUtils;

namespace Microsoft.SourceLink.GitWeb.UnitTests
{
public class TranslateRepositoryUrlsTests
{
[Fact]
public void Translate()
{
var engine = new MockEngine();

var task = new TranslateRepositoryUrls()
{
BuildEngine = engine,
RepositoryUrl = "ssh://git@src.intranet.company.com/root_dir_name/sub_dirs/reponame.git",
IsSingleProvider = true,
SourceRoots = new[]
{
new MockItem("/1/", KVP("SourceControl", "git"), KVP("ScmRepositoryUrl", "ssh://git@src.intranet.company.com/root_dir_name/sub_dirs/reponame.git")),
new MockItem("/2/", KVP("SourceControl", "tfvc"), KVP("ScmRepositoryUrl", "ssh://git@src.intranet.company1.com/root_dir_name/sub_dirs/reponame.git")),
new MockItem("/2/", KVP("SourceControl", "git"), KVP("ScmRepositoryUrl", "ssh://git@src.intranet.company1.com/root_dir_name/sub_dirs/reponame.git")),
new MockItem("/2/", KVP("SourceControl", "tfvc"), KVP("ScmRepositoryUrl", "ssh://git@src.intranet.company2.com/root_dir_name/sub_dirs/reponame.git")),
},
Hosts = new[]
{
new MockItem("src.intranet.company1.com")
}
};

bool result = task.Execute();
AssertEx.AssertEqualToleratingWhitespaceDifferences("", engine.Log);

AssertEx.AreEqual("https://src.intranet.company.com/root_dir_name/sub_dirs/reponame.git", task.TranslatedRepositoryUrl);

AssertEx.Equal(new[]
{
"https://src.intranet.company.com/root_dir_name/sub_dirs/reponame.git",
"ssh://git@src.intranet.company1.com/root_dir_name/sub_dirs/reponame.git",
"https://src.intranet.company1.com/root_dir_name/sub_dirs/reponame.git",
"ssh://git@src.intranet.company2.com/root_dir_name/sub_dirs/reponame.git",
}, task.TranslatedSourceRoots.Select(r => r.GetMetadata("ScmRepositoryUrl")));

Assert.True(result);
}
}
}
35 changes: 35 additions & 0 deletions src/SourceLink.GitWeb/GetSourceLinkUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See
// License.txt in the project root for license information.

using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks.SourceControl;

namespace Microsoft.SourceLink.GitWeb
{
/// <summary>
/// The task calculates SourceLink URL for a given SourceRoot. If the SourceRoot is associated
/// with a git repository with a recognized domain the <see cref="SourceLinkUrl"/> output
/// property is set to the content URL corresponding to the domain, otherwise it is set to
/// string "N/A".
/// </summary>
public sealed class GetSourceLinkUrl : GetSourceLinkUrlGitTask
{
protected override string HostsItemGroupName => "SourceLinkGitWebHost";
protected override string ProviderDisplayName => "GitWeb";

protected override string BuildSourceLinkUrl(Uri contentUri, Uri gitUri, string relativeUrl, string revisionId, ITaskItem hostItem)
{
var trimLeadingSlash = relativeUrl.TrimStart('/', '\\');
var trimmedContentUrl = contentUri.ToString().TrimEnd('/', '\\');

/* p = project/path
* a = action
* hb = SHA/revision
* f = repo file path
*/
var gitwebRawUrl = $"{trimmedContentUrl}/?p={trimLeadingSlash}.git;a=blob_plain;hb={revisionId};f=*";
return gitwebRawUrl;
}
}
}
32 changes: 32 additions & 0 deletions src/SourceLink.GitWeb/Microsoft.SourceLink.GitWeb.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<AutoGenerateAssemblyVersion>true</AutoGenerateAssemblyVersion>

<!-- Using an explicit nuspec file due to https://github.com/NuGet/Home/issues/6754 -->
<IsPackable>true</IsPackable>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
<NuspecBasePath>$(OutputPath)</NuspecBasePath>

<PackageDescription>Generates source link for Git repositories using a GitWeb server.</PackageDescription>
<PackageTags>MSBuild Tasks GitWeb source link</PackageTags>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Common\Names.cs" Link="Common\Names.cs" />
<Compile Include="..\Common\GetSourceLinkUrlGitTask.cs" Link="Common\GetSourceLinkUrlGitTask.cs" />
<Compile Include="..\Common\TranslateRepositoryUrlGitTask.cs" Link="Common\TranslateRepositoryUrlGitTask.cs" />
<Compile Include="..\Common\UriUtilities.cs" Link="Common\UriUtilities.cs" />
<EmbeddedResource Include="..\Common\CommonResources.resx" Link="Common\CommonResources.resx">
<Namespace>Microsoft.Build.Tasks.SourceControl</Namespace>
<GenerateSource>true</GenerateSource>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(MicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MicrosoftBuildTasksCore)" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.SourceLink.GitWeb.UnitTests" />
</ItemGroup>
</Project>
17 changes: 17 additions & 0 deletions src/SourceLink.GitWeb/Microsoft.SourceLink.GitWeb.nuspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
$CommonMetadataElements$
<dependencies>
<dependency id="Microsoft.SourceLink.Common" version="$Version$" />
<dependency id="Microsoft.Build.Tasks.Git" version="$Version$" />
</dependencies>
</metadata>
<files>
$CommonFileElements$
<file src="**\SourceLink.GitWeb.*" target="tools" />

<file src="$ProjectDirectory$\build\*.*" target="build" />
<file src="$ProjectDirectory$\buildMultiTargeting\*.*" target="buildMultiTargeting" />
</files>
</package>
11 changes: 11 additions & 0 deletions src/SourceLink.GitWeb/TranslateRepositoryUrls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See
// License.txt in the project root for license information.

using Microsoft.Build.Tasks.SourceControl;

namespace Microsoft.SourceLink.GitWeb
{
public sealed class TranslateRepositoryUrls : TranslateRepositoryUrlsGitTask
{
}
}
65 changes: 65 additions & 0 deletions src/SourceLink.GitWeb/build/SourceLink.GitWeb.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<_SourceLinkGitWebAssemblyFile Condition="'$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tools\net461\Microsoft.SourceLink.GitWeb.dll</_SourceLinkGitWebAssemblyFile>
<_SourceLinkGitWebAssemblyFile Condition="'$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\Microsoft.SourceLink.GitWeb.dll</_SourceLinkGitWebAssemblyFile>
</PropertyGroup>

<UsingTask TaskName="Microsoft.SourceLink.GitWeb.GetSourceLinkUrl" AssemblyFile="$(_SourceLinkGitWebAssemblyFile)" />
<UsingTask TaskName="Microsoft.SourceLink.GitWeb.TranslateRepositoryUrls" AssemblyFile="$(_SourceLinkGitWebAssemblyFile)" />

<PropertyGroup>
<SourceLinkUrlInitializerTargets>$(SourceLinkUrlInitializerTargets);_InitializeGitWebSourceLinkUrl</SourceLinkUrlInitializerTargets>
<SourceControlManagerUrlTranslationTargets>$(SourceControlManagerUrlTranslationTargets);TranslateGitWebUrlsInSourceControlInformation</SourceControlManagerUrlTranslationTargets>
</PropertyGroup>

<Target Name="_InitializeGitWebSourceLinkUrl" Inputs="@(SourceRoot)" Outputs="|%(Identity)|">
<!--
The task calculates SourceLink URL for a given SourceRoot.
If the SourceRoot is associated with a git repository with a recognized domain the <see cref="SourceLinkUrl" />
output property is set to the content URL corresponding to the domain, otherwise it is set to string "N/A".
Recognized domains are specified via Hosts (initialized from SourceLinkGitWebHost item group).
In addition, if SourceLinkHasSingleProvider is true an implicit host is parsed from RepositoryUrl.
Example of SourceLinkGitWebHost items:
<ItemGroup>
<SourceLinkGitWebHost Include="myGitWeb1.com" ContentUrl="http://myGitWeb1.com" />
<SourceLinkGitWebHost Include="myGitWeb2.com" /> ContentUrl defaults to https://myGitWeb2.com
<SourceLinkGitWebHost Include="myGitWeb3.com:8080" /> ContentUrl defaults to https://myGitWeb3.com:8080
</ItemGroup>
ContentUrl is optional. If not specified it defaults to "https://{domain}" or "http://{domain}", based on the scheme of SourceRoot.RepositoryUrl.
-->
<Microsoft.SourceLink.GitWeb.GetSourceLinkUrl RepositoryUrl="$(PrivateRepositoryUrl)" SourceRoot="@(SourceRoot)" Hosts="@(SourceLinkGitWebHost)" IsSingleProvider="$(SourceLinkHasSingleProvider)">
<Output TaskParameter="SourceLinkUrl" PropertyName="_SourceLinkUrlToUpdate" />
</Microsoft.SourceLink.GitWeb.GetSourceLinkUrl>

<ItemGroup>
<!-- Only update the SourceLinkUrl metadata if the SourceRoot belongs to this source control -->
<SourceRoot Update="%(Identity)" SourceLinkUrl="$(_SourceLinkUrlToUpdate)" Condition="'$(_SourceLinkUrlToUpdate)' != 'N/A'" />
</ItemGroup>
</Target>

<!--
We need to translate ssh URLs to https.
-->
<Target Name="TranslateGitWebUrlsInSourceControlInformation">

<ItemGroup>
<_TranslatedSourceRoot Remove="@(_TranslatedSourceRoot)" />
</ItemGroup>

<Microsoft.SourceLink.GitWeb.TranslateRepositoryUrls RepositoryUrl="$(ScmRepositoryUrl)" SourceRoots="@(SourceRoot)" Hosts="@(SourceLinkGitWebHost)" IsSingleProvider="$(SourceLinkHasSingleProvider)">
<Output TaskParameter="TranslatedRepositoryUrl" PropertyName="ScmRepositoryUrl" />
<Output TaskParameter="TranslatedSourceRoots" ItemName="_TranslatedSourceRoot" />
</Microsoft.SourceLink.GitWeb.TranslateRepositoryUrls>

<ItemGroup>
<SourceRoot Remove="@(SourceRoot)" />
<SourceRoot Include="@(_TranslatedSourceRoot)" />
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<Import Project="..\build\$(MSBuildThisFileName).targets"/>
</Project>
32 changes: 32 additions & 0 deletions src/SourceLink.GitWeb/xlf/Resources.cs.xlf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="cs" original="../Resources.resx">
<body>
<trans-unit id="AtLeastOneRepositoryHostIsRequired">
<source>{0} item group is empty. At least one {1} repository host is required in order to generate SourceLink.</source>
<target state="translated">Skupina položek {0} je prázdná. Kvůli generování zdrojového odkazu se vyžaduje alespoň jeden hostitel úložiště {1}.</target>
<note />
</trans-unit>
<trans-unit id="SpecifiesAnInvalidUrl">
<source>{0} specifies an invalid URL: '{1}'</source>
<target state="translated">{0} určuje neplatnou adresu URL: {1}.</target>
<note />
</trans-unit>
<trans-unit id="ValueOfWithIdentityIsInvalid">
<source>The value of {0} with identity '{1}' is invalid: '{2}'"</source>
<target state="translated">Hodnota {0} s identitou {1} je neplatná: {2}"</target>
<note />
</trans-unit>
<trans-unit id="ValueOfWithIdentityIsNotValidCommitHash">
<source>The value of {0} with identity '{1}' is not a valid commit hash: '{2}'"</source>
<target state="translated">Hodnota {0} s identitou {1} není platná hodnota hash potvrzení: {2}"</target>
<note />
</trans-unit>
<trans-unit id="ValuePassedToTaskParameterNotValidDomainName">
<source>The value passed to task parameter {0} is not a valid host name: '{1}'</source>
<target state="translated">Hodnota předaná parametru úlohy {0} není platný název hostitele: {1}.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
32 changes: 32 additions & 0 deletions src/SourceLink.GitWeb/xlf/Resources.de.xlf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="de" original="../Resources.resx">
<body>
<trans-unit id="AtLeastOneRepositoryHostIsRequired">
<source>{0} item group is empty. At least one {1} repository host is required in order to generate SourceLink.</source>
<target state="translated">Die Elementgruppe "{0}" ist leer. Mindestens ein {1}-Repositoryhost wird benötigt, um SourceLink zu generieren.</target>
<note />
</trans-unit>
<trans-unit id="SpecifiesAnInvalidUrl">
<source>{0} specifies an invalid URL: '{1}'</source>
<target state="translated">"{0}" gibt eine ungültige URL an: {1}</target>
<note />
</trans-unit>
<trans-unit id="ValueOfWithIdentityIsInvalid">
<source>The value of {0} with identity '{1}' is invalid: '{2}'"</source>
<target state="translated">Der Wert von "{0}" mit der Identität "{1}" ist ungültig: "{2}"</target>
<note />
</trans-unit>
<trans-unit id="ValueOfWithIdentityIsNotValidCommitHash">
<source>The value of {0} with identity '{1}' is not a valid commit hash: '{2}'"</source>
<target state="translated">Der Wert von "{0}" mit der Identität "{1}" ist kein gültiger Commithash: "{2}"</target>
<note />
</trans-unit>
<trans-unit id="ValuePassedToTaskParameterNotValidDomainName">
<source>The value passed to task parameter {0} is not a valid host name: '{1}'</source>
<target state="translated">Der an den Taskparameter "{0}" übergebene Wert ist kein gültiger Hostname: "{1}"</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

0 comments on commit e1b2261

Please sign in to comment.