diff --git a/Documentation/tutorial/docfx_getting_started.md b/Documentation/tutorial/docfx_getting_started.md index 68ad12a3947..6e4154e9b50 100644 --- a/Documentation/tutorial/docfx_getting_started.md +++ b/Documentation/tutorial/docfx_getting_started.md @@ -64,7 +64,27 @@ namespace WebApplication1 *Step4.* Right click on the website project, and click *View* -> *View in Browser*, navigate to `/_site` sub URL to view your website! -4. Build from source code +4. Use *DocFX* with a Build Server +--------------- + +*DocFX* can be used in a Continuous Integration environment. + +Most build systems do not checkout the branch that is being built, but +use a `detached head` for the specific commit. DoxFX needs the the branch name to implement the `View Source` link in the API documentation. + +Setting the environment variable `DOCFX_SOURCE_BRANCH_NAME` tells DocFX which branch name to use. + +Many build systems set an environment variable with the branch name. DocFX uses the following: + +- `APPVEYOR_REPO_BRANCH` - [AppVeyor](https://www.appveyor.com/) +- `BUILD_SOURCEBRANCHNAME` - [Visual Studio Online](https://www.visualstudio.com/vso/) +- `CI_BUILD_REF_NAME` - [GitLab CI](https://about.gitlab.com/gitlab-ci/) +- `Git_Branch` - [TeamCity](https://www.jetbrains.com/teamcity/) +- `GIT_BRANCH` - [Jenkins](https://jenkins.io/) +- `GIT_LOCAL_BRANCH` - [Jenkins](https://jenkins.io/) + + +5. Build from source code ---------------- As a prerequisite, you need: - [Microsoft Build Tools 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48159) @@ -80,7 +100,7 @@ As a prerequisite, you need: *Step4.* Follow steps in #2, #3, #4 to use *DocFX* in command-line, IDE or .NET Core. -5. A seed project to play with *DocFX* +6. A seed project to play with *DocFX* ------------------------- Here is a seed project https://github.com/docascode/docfx-seed. It contains @@ -93,7 +113,7 @@ Here is a seed project https://github.com/docascode/docfx-seed. It contains > [!Tip] > It is a good practice to separate files with different type into different folders. -6. Q&A +7. Q&A ------------------------- 1. Q: How do I quickly reference APIs from other APIs or conceptual files? A: Use `@uid` syntax. diff --git a/src/Microsoft.DocAsCode.Common/Git/GitUtility.cs b/src/Microsoft.DocAsCode.Common/Git/GitUtility.cs index a7614c49d47..ae41581e01e 100644 --- a/src/Microsoft.DocAsCode.Common/Git/GitUtility.cs +++ b/src/Microsoft.DocAsCode.Common/Git/GitUtility.cs @@ -1,9 +1,10 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Microsoft.DocAsCode.Common.Git { using System; + using System.Linq; using System.IO; using System.Text; using System.Collections.Concurrent; @@ -22,6 +23,16 @@ public static class GitUtility private static readonly string GetOriginUrlCommand = "config --get remote.origin.url"; private static readonly string GetLocalHeadIdCommand = "rev-parse HEAD"; private static readonly string GetRemoteHeadIdCommand = "rev-parse @{u}"; + + private static readonly string[] BuildSystemBranchName = new[] + { + "APPVEYOR_REPO_BRANCH", // AppVeyor + "Git_Branch", // Team City + "CI_BUILD_REF_NAME", // GitLab CI + "GIT_LOCAL_BRANCH", // Jenkins + "GIT_BRANCH", // Jenkins + "BUILD_SOURCEBRANCHNAME" // VSO Agent + }; private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); @@ -112,12 +123,70 @@ private static GitDetail GetFileDetailCore(string filePath) private static GitRepoInfo GetRepoInfoCore(string directory) { var repoRootPath = RunGitCommandAndGetFirstLine(directory, GetRepoRootCommand); - var localBranch = GetLocalBranchName(repoRootPath); + var originUrl = RunGitCommandAndGetFirstLine(repoRootPath, GetOriginUrlCommand); + var repoInfo = new GitRepoInfo + { + // TODO: remove commit id to avoid config hash changed + //LocalHeadCommitId = RunGitCommandAndGetFirstLine(repoRootPath, GetLocalHeadIdCommand), + //RemoteHeadCommitId = TryRunGitCommandAndGetFirstLine(repoRootPath, GetRemoteHeadIdCommand), + RemoteOriginUrl = originUrl, + RepoRootPath = repoRootPath + }; + + return GetBranchNames(repoInfo); + } + + private static GitRepoInfo GetBranchNames(GitRepoInfo repo) + { + bool isDetachedHead = false; + + // The "docfx..". environment variable specifies the branch name to use. + var localBranch = Environment.GetEnvironmentVariable("DOCFX_SOURCE_BRANCH_NAME"); + if (!string.IsNullOrEmpty(localBranch)) + { + Logger.LogInfo($"Using branch '{localBranch}' from the environment variable DOCFX_SOURCE_BRANCH_NAME."); + } + + // Many build systems use a "detached head", which means that the normal git commands + // to get branch names do not work. Thankfully, they set an environment variable. + if (string.IsNullOrEmpty(localBranch)) + { + isDetachedHead = "HEAD" == RunGitCommandAndGetFirstLine(repo.RepoRootPath, GetLocalBranchCommand); + if (isDetachedHead) + { + foreach (var name in BuildSystemBranchName) + { + localBranch = Environment.GetEnvironmentVariable(name); + if (!string.IsNullOrEmpty(localBranch)) + { + Logger.LogInfo($"Using branch '{localBranch}' from the environment variable {name}."); + break; + } + } + } + } + + // Fallback to using commit id. + if (isDetachedHead && string.IsNullOrEmpty(localBranch)) + { + localBranch = RunGitCommandAndGetFirstLine(repo.RepoRootPath, GetLocalBranchCommitIdCommand); + Logger.LogInfo("Fallback to use commit id as the branch name."); + } + + // If an override, then remote branch name is same as local branch name. + if (!string.IsNullOrEmpty(localBranch)) + { + repo.LocalBranch = localBranch; + repo.RemoteBranch = localBranch; + return repo; + } + // Not a detached head. Use standard git commands to get the branch names. + localBranch = RunGitCommandAndGetFirstLine(repo.RepoRootPath, GetLocalBranchCommand); string remoteBranch; try { - remoteBranch = RunGitCommandAndGetFirstLine(repoRootPath, GetRemoteBranchCommand); + remoteBranch = RunGitCommandAndGetFirstLine(repo.RepoRootPath, GetRemoteBranchCommand); var index = remoteBranch.IndexOf('/'); if (index > 0) { @@ -130,31 +199,9 @@ private static GitRepoInfo GetRepoInfoCore(string directory) remoteBranch = localBranch; } - var originUrl = RunGitCommandAndGetFirstLine(repoRootPath, GetOriginUrlCommand); - - return new GitRepoInfo - { - LocalBranch = localBranch, - // TODO: remove commit id to avoid config hash changed - //LocalHeadCommitId = RunGitCommandAndGetFirstLine(repoRootPath, GetLocalHeadIdCommand), - //RemoteHeadCommitId = TryRunGitCommandAndGetFirstLine(repoRootPath, GetRemoteHeadIdCommand), - RemoteOriginUrl = originUrl, - RepoRootPath = repoRootPath, - RemoteBranch = remoteBranch, - }; - } - - private static string GetLocalBranchName(string repoRootPath) - { - var localBranch = RunGitCommandAndGetFirstLine(repoRootPath, GetLocalBranchCommand); - - // Fallback to use commit id - if (localBranch.Equals("HEAD")) - { - localBranch = RunGitCommandAndGetFirstLine(repoRootPath, GetLocalBranchCommitIdCommand); - Logger.LogInfo("Fallback to use commit id as the branch name."); - } - return localBranch; + repo.LocalBranch = localBranch; + repo.RemoteBranch = remoteBranch; + return repo; } private static void ProcessErrorMessage(string message) diff --git a/test/Microsoft.DocAsCode.Common.Tests/GitUtilityTest.cs b/test/Microsoft.DocAsCode.Common.Tests/GitUtilityTest.cs new file mode 100644 index 00000000000..b62fbf5cd94 --- /dev/null +++ b/test/Microsoft.DocAsCode.Common.Tests/GitUtilityTest.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DocAsCode.Common.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + + using Xunit; + using YamlDotNet.Core; + + using Microsoft.DocAsCode.Common.Git; + using Microsoft.DocAsCode.YamlSerialization; + + [Trait("Owner", "makaretu")] + public class GitUtilityTest + { + [Fact] + public void Environment_ForBranchName() + { + const string envName = "DOCFX_SOURCE_BRANCH_NAME"; + var original = Environment.GetEnvironmentVariable(envName); + try + { + Environment.SetEnvironmentVariable(envName, "special-branch"); + var info = GitUtility.GetFileDetail(Directory.GetCurrentDirectory()); + Assert.Equal("special-branch", info.RemoteBranch); + } + finally + { + Environment.SetEnvironmentVariable(envName, original); + } + } + + } +}