diff --git a/CHANGELOG.md b/CHANGELOG.md index ede7afe619..8751f5c996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## Version `v0.27.19` * ![Enhancement][badge-enhancement] On the HTML search page, pressing enter no longer causes the page to refresh (and therefore does not trigger the slow search index rebuild). ([#1728][github-1728], [#1833][github-1833], [#1834][github-1834]) +* ![Enhancement][badge-enhancement] For the `edit_link` keyword to `HTML()`, Documenter automatically tries to figure out if the remote default branch is `main`, `master`, or something else. It will print a warning if it is unable to reliably determine either `edit_link` or `devbranch` (for `deploydocs`). ([#1827][github-1827], [#1829][github-1829]) ## Version `v0.27.18` @@ -1046,7 +1047,9 @@ [github-1818]: https://github.com/JuliaDocs/Documenter.jl/pull/1818 [github-1821]: https://github.com/JuliaDocs/Documenter.jl/pull/1821 [github-1825]: https://github.com/JuliaDocs/Documenter.jl/pull/1825 +[github-1827]: https://github.com/JuliaDocs/Documenter.jl/issues/1827 [github-1828]: https://github.com/JuliaDocs/Documenter.jl/pull/1828 +[github-1829]: https://github.com/JuliaDocs/Documenter.jl/pull/1829 [github-1833]: https://github.com/JuliaDocs/Documenter.jl/pull/1833 [github-1834]: https://github.com/JuliaDocs/Documenter.jl/pull/1834 diff --git a/src/Documenter.jl b/src/Documenter.jl index 54fee2e394..6dde3264f3 100644 --- a/src/Documenter.jl +++ b/src/Documenter.jl @@ -532,18 +532,7 @@ function deploydocs(; # Try to figure out default branch (see #1443 and #1727) if devbranch === nothing - env = copy(ENV) - env["GIT_TERMINAL_PROMPT"] = "0" - env["GIT_SSH_COMMAND"] = get(env, "GIT_SSH_COMMAND", "ssh -o \"BatchMode yes\"") - str = try - read(pipeline(ignorestatus( - setenv(`git remote show origin`, env; dir=root) - ); stderr=devnull), String) - catch - "" - end - m = match(r"^\s*HEAD branch:\s*(.*)$"m, str) - devbranch = m === nothing ? "master" : String(m[1]) + devbranch = Utilities.git_remote_head_branch("deploydocs(devbranch = ...)", root) end deploy_decision = deploy_folder(deploy_config; diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index c4cdbf7346..c5187b9277 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -785,6 +785,55 @@ function check_strict_kw(strict) return nothing end +""" +Calls `git remote show \$(remotename)` to try to determine the main (development) branch +of the remote repository. Returns `master` and prints a warning if it was unable to figure +it out automatically. + +`root` is the the directory where `git` gets run. `varname` is just informational and used +to construct the warning messages. +""" +function git_remote_head_branch(varname, root; remotename = "origin", fallback = "master") + env = copy(ENV) + env["GIT_TERMINAL_PROMPT"] = "0" + env["GIT_SSH_COMMAND"] = get(env, "GIT_SSH_COMMAND", "ssh -o \"BatchMode yes\"") + cmd = `git remote show $(remotename)` + stderr_output = IOBuffer() + git_remote_output = try + # Older Julia versions don't support pipeline-ing into IOBuffer. + stderr_redirect = if VERSION >= v"1.6.0" + stderr_output + else + write(stderr_output, "(no output redirect on Julia $VERSION)") + devnull + end + read(pipeline(setenv(cmd, env; dir=root); stderr = stderr_redirect), String) + catch e + @warn """ + Unable to determine $(varname) from remote HEAD branch, defaulting to "$(fallback)". + Calling `git remote` failed with an exception. Set JULIA_DEBUG=Documenter to see the error. + Unless this is due to a configuration error, the relevant variable should be set explicitly. + """ + @debug "Command: $cmd" exception = (e, catch_backtrace()) stderr = String(take!(stderr_output)) + return fallback + end + m = match(r"^\s*HEAD branch:\s*(.*)$"m, git_remote_output) + if m === nothing + @warn """ + Unable to determine $(varname) from remote HEAD branch, defaulting to "$(fallback)". + Failed to parse the `git remote` output. Set JULIA_DEBUG=Documenter to see the output. + Unless this is due to a configuration error, the relevant variable should be set explicitly. + """ + @debug """ + stdout from $cmd: + $(git_remote_output) + """ + fallback + else + String(m[1]) + end +end + include("DOM.jl") include("MDFlatten.jl") include("TextDiff.jl") diff --git a/src/Writers/HTMLWriter.jl b/src/Writers/HTMLWriter.jl index 3b5bf3d3da..688b6853ac 100644 --- a/src/Writers/HTMLWriter.jl +++ b/src/Writers/HTMLWriter.jl @@ -420,7 +420,7 @@ struct HTML <: Documenter.Writer function HTML(; prettyurls :: Bool = true, disable_git :: Bool = false, - edit_link :: Union{String, Symbol, Nothing, Default} = Default("master"), + edit_link :: Union{String, Symbol, Nothing, Default} = Default(Utilities.git_remote_head_branch("HTML(edit_link = ...)", Utilities.currentdir())), canonical :: Union{String, Nothing} = nothing, assets :: Vector = String[], analytics :: String = "", diff --git a/test/TestUtilities.jl b/test/TestUtilities.jl index cc8b10f048..dacc18f9d5 100644 --- a/test/TestUtilities.jl +++ b/test/TestUtilities.jl @@ -2,7 +2,7 @@ module TestUtilities using Test import IOCapture -export @quietly +export @quietly, trun const QUIETLY_LOG = joinpath(@__DIR__, "quietly.log") @@ -85,4 +85,27 @@ function is_success(x) return false end +function trun(cmd::Base.AbstractCmd) + buffer = IOBuffer() + cmd_redirected = if VERSION < v"1.6.0" + # Okay, older Julia versions have a missing method, which don't allow a redirect: + # MethodError: no method matching rawhandle(::Base.GenericIOBuffer{Array{UInt8,1}}) + # Not sure when it was exactly fixed, but 1.6 seems to work fine. + write(buffer, "no output recorded") + cmd + else + pipeline(cmd; stdin = devnull, stdout = buffer, stderr = buffer) + end + try + run(cmd_redirected) + return true + catch e + @error """ + Running external program $(cmd) failed, output: + $(String(take!(buffer))) + """ exception = (e, catch_backtrace()) + return false + end +end + end diff --git a/test/doctests/doctests.jl b/test/doctests/doctests.jl index be4151eca4..7571f4c0cf 100644 --- a/test/doctests/doctests.jl +++ b/test/doctests/doctests.jl @@ -36,6 +36,7 @@ function run_makedocs(f, mdfiles, modules=Module[]; kwargs...) c = IOCapture.capture(rethrow = InterruptException) do makedocs( sitename = " ", + format = Documenter.HTML(edit_link = "master"), root = dir, modules = modules; kwargs... diff --git a/test/utilities.jl b/test/utilities.jl index 4430d79f6d..5024158f3b 100644 --- a/test/utilities.jl +++ b/test/utilities.jl @@ -170,15 +170,15 @@ end mkpath(path_repo) cd(path_repo) do # Create a simple mock repo in a temporary directory with a single file. - @test success(`git init`) - @test success(`git config user.email "tester@example.com"`) - @test success(`git config user.name "Test Committer"`) - @test success(`git remote add origin git@github.com:JuliaDocs/Documenter.jl.git`) + @test trun(`git init`) + @test trun(`git config user.email "tester@example.com"`) + @test trun(`git config user.name "Test Committer"`) + @test trun(`git remote add origin git@github.com:JuliaDocs/Documenter.jl.git`) mkpath("src") filepath = abspath(joinpath("src", "SourceFile.jl")) write(filepath, "X") - @test success(`git add -A`) - @test success(`git commit -m"Initial commit."`) + @test trun(`git add -A`) + @test trun(`git commit -m"Initial commit."`) # Run tests commit = Documenter.Utilities.repo_commit(filepath) @@ -198,7 +198,7 @@ end # Test worktree path_worktree = joinpath(path, "worktree") cd("$(path_repo)") do - @test success(`git worktree add $(path_worktree)`) + @test trun(`git worktree add $(path_worktree)`) end cd("$(path_worktree)") do filepath = abspath(joinpath("src", "SourceFile.jl")) @@ -221,15 +221,15 @@ end path_submodule = joinpath(path, "submodule") mkpath(path_submodule) cd(path_submodule) do - @test success(`git init`) - @test success(`git config user.email "tester@example.com"`) - @test success(`git config user.name "Test Committer"`) + @test trun(`git init`) + @test trun(`git config user.email "tester@example.com"`) + @test trun(`git config user.name "Test Committer"`) # NOTE: the target path in the `git submodule add` command is necessary for # Windows builds, since otherwise Git claims that the path is in a .gitignore # file. - @test success(`git submodule add $(path_repo) repository`) - @test success(`git add -A`) - @test success(`git commit -m"Initial commit."`) + @test trun(`git submodule add $(path_repo) repository`) + @test trun(`git add -A`) + @test trun(`git commit -m"Initial commit."`) end path_submodule_repo = joinpath(path, "submodule", "repository") @test isdir(path_submodule_repo) @@ -539,6 +539,53 @@ end @test err.msg == "tag :foo is not a valid Documenter error" end end + + @testset "git_remote_head_branch" begin + + function git_create_bare_repo(path; head = nothing) + mkdir(path) + @test trun(`git -C $(path) init --bare`) + @test isfile(joinpath(path, "HEAD")) + if head !== nothing + write(joinpath(path, "HEAD"), """ + ref: refs/heads/$(head) + """) + end + mktempdir() do subdir_path + # We need to commit something to the non-standard branch to actually + # "activate" the non-standard HEAD: + head = (head === nothing) ? "master" : head + @test trun(`git clone $(path) $(subdir_path)`) + @test trun(`git -C $(subdir_path) config user.email "tester@example.com"`) + @test trun(`git -C $(subdir_path) config user.name "Test Committer"`) + @test trun(`git -C $(subdir_path) checkout -b $(head)`) + @test trun(`git -C $(subdir_path) commit --allow-empty -m"initial empty commit"`) + @test trun(`git -C $(subdir_path) push --set-upstream origin $(head)`) + end + end + + mktempdir() do path + cd(path) do + # If there is no parent remote repository, we should get a warning and the fallback value: + @test (@test_logs (:warn,) Documenter.Utilities.git_remote_head_branch(".", pwd(); fallback = "fallback")) == "fallback" + @test (@test_logs (:warn,) Documenter.Utilities.git_remote_head_branch(".", pwd())) == "master" + # We'll set up two "remote" bare repositories with non-standard HEADs: + git_create_bare_repo("barerepo", head = "maindevbranch") + git_create_bare_repo("barerepo_other", head = "main") + # Clone barerepo and test git_remote_head_branch: + @test trun(`git clone barerepo/ local/`) + @test Documenter.Utilities.git_remote_head_branch(".", "local") == "maindevbranch" + # Now, let's add the other repo as another remote, and fetch the HEAD for that: + @test trun(`git -C local/ remote add other ../barerepo_other/`) + @test trun(`git -C local/ fetch other`) + @test Documenter.Utilities.git_remote_head_branch(".", "local") == "maindevbranch" + @test Documenter.Utilities.git_remote_head_branch(".", "local"; remotename = "other") == "main" + # Asking for a nonsense remote should also warn and drop back to fallback: + @test (@test_logs (:warn,) Documenter.Utilities.git_remote_head_branch(".", pwd(); remotename = "nonsense", fallback = "fallback")) == "fallback" + @test (@test_logs (:warn,) Documenter.Utilities.git_remote_head_branch(".", pwd(); remotename = "nonsense")) == "master" + end + end + end end end