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

feat: Support DOCFX_SOURCE_REPOSITORY_URL #9759

Merged
merged 7 commits into from
Apr 1, 2024
5 changes: 5 additions & 0 deletions docs/reference/docfx-environment-variables-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ If set true. Keep following debug info in output HTML.

Used to override git branch name.

## `DOCFX_SOURCE_REPOSITORY_URL`

Used to override git organization and repository names.
It must be defined in the `https://{host_name}/{organization}/{repository_name}` format (e.g.`https://github.com/dotnet/docfx`).

## `DOCFX_NO_CHECK_CERTIFICATE_REVOCATION_LIST`

Used to disable CRL check.
Expand Down
57 changes: 54 additions & 3 deletions src/Docfx.Common/Git/GitUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ record Repo(string path, string url, string branch);
if (repo is null)
return null;

var repoUrl = ResolveDocfxSourceRepoUrl(repo.url);

return new()
{
Repo = repo.url,
Repo = repoUrl,
Branch = repo.branch,
Path = Path.GetRelativePath(repo.path, filePath).Replace('\\', '/'),
};
Expand All @@ -49,10 +51,12 @@ record Repo(string path, string url, string branch);
public static string? RawContentUrlToContentUrl(string rawUrl)
{
// GitHub
return Regex.Replace(
var url = Regex.Replace(
rawUrl,
@"^https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+)$",
string.IsNullOrEmpty(s_branch) ? "https://github.com/$1/$2/blob/$3/$4" : $"https://github.com/$1/$2/blob/{s_branch}/$4");

return ResolveDocfxSourceRepoUrl(url);
}

public static string? GetSourceUrl(GitSource source)
Expand All @@ -65,7 +69,7 @@ record Repo(string path, string url, string branch);

var path = source.Path.Replace('\\', '/');

return url.Host switch
var sourceUrl = url.Host switch
{
"github.com" => $"https://github.com{url.AbsolutePath}/blob/{source.Branch}/{path}{(source.Line > 0 ? $"#L{source.Line}" : null)}",
"bitbucket.org" => $"https://bitbucket.org{url.AbsolutePath}/src/{source.Branch}/{path}{(source.Line > 0 ? $"#lines-{source.Line}" : null)}",
Expand All @@ -74,6 +78,11 @@ _ when url.Host.EndsWith(".visualstudio.com") || url.Host == "dev.azure.com" =>
_ => null,
};

if (sourceUrl == null)
return null;

return ResolveDocfxSourceRepoUrl(sourceUrl);

static bool IsCommit(string branch)
{
return branch.Length == 40 && branch.All(char.IsLetterOrDigit);
Expand Down Expand Up @@ -173,4 +182,46 @@ static string GitUrlToHttps(string url)
}
}
}

/// <summary>
/// Rewrite path if `DOCFX_SOURCE_REPOSITORY_URL` environment variable is specified.
/// </summary>
private static string ResolveDocfxSourceRepoUrl(string originalUrl)
{
var docfxSourceRepoUrl = Environment.GetEnvironmentVariable("DOCFX_SOURCE_REPOSITORY_URL");
if (docfxSourceRepoUrl == null)
return originalUrl;

if (!Uri.TryCreate(originalUrl, UriKind.Absolute, out var parsedOriginalUrl)
|| !Uri.TryCreate(docfxSourceRepoUrl, UriKind.Absolute, out var parsedOverrideUrl)
|| parsedOriginalUrl.Host != parsedOverrideUrl.Host)
{
return originalUrl;
}

// Parse value that defined with `{orgName}/{repoName}` format.
var parts = parsedOverrideUrl.LocalPath.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length < 2)
return originalUrl;

string orgName = parts[0];
string repoName = parts[1];

switch (parsedOriginalUrl.Host)
{
case "github.com":
case "bitbucket.org":
case "dev.azure.com":
{
// Replace `/{orgName}/{repoName}` and remove `.git` suffix.
var builder = new UriBuilder(parsedOriginalUrl);
builder.Path = Regex.Replace(builder.Path.TrimEnd(".git"), @"^/[^/]+/[^/]+", $"/{orgName}/{repoName}");
return builder.Uri.ToString();
}

// Currently other URL formats are not supported (e.g. visualstudio.com, GitHub Enterprise Server)
default:
return originalUrl;
}
}
}
64 changes: 64 additions & 0 deletions test/Docfx.Common.Tests/GitUtilityWithSourceRepoUrlTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Docfx.Common.Git;
using Xunit;

namespace Docfx.Common.Tests;

[Collection("docfx STA")]
public class GitUtilityWithSourceRepositoryUrlTest : IDisposable
{
private readonly string _originalBranchName;
private readonly string _originalSourceRepoUrl;

private const string ORG_NAME = "dotnet";
private const string REPO_NAME = "docfx";

private const string BRANCH_NAME = "special-branch";
private const string DOCFX_SOURCE_BRANCH_NAME = nameof(DOCFX_SOURCE_BRANCH_NAME);
private const string DOCFX_SOURCE_REPOSITORY_URL = nameof(DOCFX_SOURCE_REPOSITORY_URL);

public GitUtilityWithSourceRepositoryUrlTest()
{
_originalBranchName = Environment.GetEnvironmentVariable(DOCFX_SOURCE_BRANCH_NAME);
_originalSourceRepoUrl = Environment.GetEnvironmentVariable(DOCFX_SOURCE_REPOSITORY_URL);

Environment.SetEnvironmentVariable(DOCFX_SOURCE_BRANCH_NAME, BRANCH_NAME);
Environment.SetEnvironmentVariable(DOCFX_SOURCE_REPOSITORY_URL, $"https://github.com/{ORG_NAME}/{REPO_NAME}");
}

public void Dispose()
{
Environment.SetEnvironmentVariable(DOCFX_SOURCE_BRANCH_NAME, _originalBranchName);
Environment.SetEnvironmentVariable(DOCFX_SOURCE_REPOSITORY_URL, _originalSourceRepoUrl);
}

[Fact]
public void TryGetFileDetailTest()
{
var info = GitUtility.TryGetFileDetail(Directory.GetCurrentDirectory());
Assert.Equal(BRANCH_NAME, info.Branch);
Assert.Equal("https://github.com/dotnet/docfx", info.Repo);
}

[Fact]
public void RawContentUrlToContentUrlTest()
{
string rawUrl = "https://raw.githubusercontent.com/dotnet/docfx/main/README.md";
string expected = "https://github.com/dotnet/docfx/blob/special-branch/README.md";

var result = GitUtility.RawContentUrlToContentUrl(rawUrl);

Assert.Equal(expected, result);
}

[Theory]
[InlineData("https://github.com/user/repo.git", "main", "path/to/file.cs", 0, $"https://github.com/{ORG_NAME}/{REPO_NAME}/blob/main/path/to/file.cs")]
[InlineData("https://github.com/user/repo.git", "main", "path/to/file.cs", 10, $"https://github.com/{ORG_NAME}/{REPO_NAME}/blob/main/path/to/file.cs#L10")]
[InlineData("git@github.com:user/repo.git", "main", "path/to/file.cs", 0, $"https://github.com/{ORG_NAME}/{REPO_NAME}/blob/main/path/to/file.cs")]
public void GetSourceUrlTest_GitHub(string repo, string branch, string path, int line, string result)
{
Assert.Equal(result, GitUtility.GetSourceUrl(new(repo, branch, path, line)));
}
}
17 changes: 16 additions & 1 deletion test/docfx.Snapshot.Tests/SamplesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,32 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Docfx.Dotnet;
using Docfx.Plugins;
using UglyToad.PdfPig;
using UglyToad.PdfPig.Actions;
using UglyToad.PdfPig.Annotations;
using UglyToad.PdfPig.Outline;

namespace Docfx.Tests;

public class SamplesTest
[Collection("docfx STA")]
[Trait("Stage", "Snapshot")]
public class SamplesTest : IDisposable
{
private static readonly string s_samplesDir = Path.GetFullPath("../../../../../samples");

private const string DOCFX_SOURCE_REPOSITORY_URL = nameof(DOCFX_SOURCE_REPOSITORY_URL);

public SamplesTest()
{
Environment.SetEnvironmentVariable(DOCFX_SOURCE_REPOSITORY_URL, "https://github.com/dotnet/docfx");
}

public void Dispose()
{
Environment.SetEnvironmentVariable(DOCFX_SOURCE_REPOSITORY_URL, null);
}

private class SamplesFactAttribute : FactAttribute
{
public SamplesFactAttribute()
Expand Down