Skip to content

Commit

Permalink
Support push preview docs. (#1180)
Browse files Browse the repository at this point in the history
Documenter can now push preview docs from PRs
(when the head branch is from the same repository).
This is enabled by passing push_preview=true to deploydocs.
Documenter will then push rendered documentation
to preview/PR## where ## is the pull request number.
  • Loading branch information
fredrikekre authored Nov 16, 2019
1 parent f9fe7e1 commit 40c0f53
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 108 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

**For upgrading:** Pass the corresponding values to the `HTML` constructor when settings the `format` keyword.

* ![Feature][badge-feature] Documenter can now deploy preview documentation from pull requests (with head branch in the same repository, i.e. not from forks). This is enabled by passing `push_preview=true` to `deploydocs`. ([#1180][github-1180])

* ![Enhancement][badge-enhancement] The Documenter HTML front end now uses [KaTeX](https://katex.org/) as the default math rendering engine. ([#1097][github-1097])

**Possible breakage:** This may break the rendering of equations that use some more esoteric features that are only supported in MathJax. It is possible to switch back to MathJax by passing `mathengine = Documenter.MathJax()` to the `HTML` constructor in the `format` keyword.
Expand All @@ -22,7 +24,7 @@

**For upgrading:** If using `edit_branch = nothing`, use `edit_link = :commit` instead. If passing a `String` to `edit_branch`, pass that to `edit_link` instead.

* ![Feature][badge-feature] Deployment is now more customizable and thus not as tied to Travis CI as before. ([#1147][github-1147], [#1171][github-1171])
* ![Feature][badge-feature] Deployment is now more customizable and thus not as tied to Travis CI as before. ([#1147][github-1147], [#1171][github-1171], [#1180][github-1180])

* ![Feature][badge-feature] Documenter now has builtin support for deploying from GitHub Actions. Documenter will autodetect the running system, unless explicitly specified. ([#1144][github-1144], [#1152][github-1152])

Expand Down Expand Up @@ -475,7 +477,7 @@
[github-1166]: https://github.com/JuliaDocs/Documenter.jl/pull/1166
[github-1171]: https://github.com/JuliaDocs/Documenter.jl/pull/1171
[github-1173]: https://github.com/JuliaDocs/Documenter.jl/pull/1173

[github-1180]: https://github.com/JuliaDocs/Documenter.jl/pull/1180

[documenterlatex]: https://github.com/JuliaDocs/DocumenterLaTeX.jl
[documentermarkdown]: https://github.com/JuliaDocs/DocumenterMarkdown.jl
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ makedocs(
deploydocs(
repo = "github.com/JuliaDocs/Documenter.jl.git",
target = "build",
push_preview = true,
)
3 changes: 1 addition & 2 deletions docs/src/man/hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,7 @@ your own by following the simple interface described below.

```@docs
Documenter.DeployConfig
Documenter.should_deploy
Documenter.git_tag
Documenter.deploy_folder
Documenter.authentication_method
Documenter.authenticated_repo_url
Documenter.documenter_key
Expand Down
43 changes: 21 additions & 22 deletions src/Documenter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ the generated html. The following entries are valied in the `versions` vector:
The second argument can be `"v^"`, to point to the maximum version docs
(as in e.g. `"stable" => "v^"`).
**`push_preview`** a boolean that specifies if preview documentation should be
deployed from pull requests or not.
# See Also
The [Hosting Documentation](@ref) section of the manual provides a step-by-step guide to
Expand All @@ -409,9 +412,11 @@ function deploydocs(;
versions = ["stable" => "v^", "v#.#", devurl => devurl],
forcepush::Bool = false,
deploy_config = auto_detect_deploy_system(),
push_preview::Bool = false,
)

if should_deploy(deploy_config; repo=repo, devbranch=devbranch)
subfolder = deploy_folder(deploy_config; repo=repo, devbranch=devbranch, push_preview=push_preview, devurl=devurl)
if subfolder !== nothing
# Add local bin path if needed.
Deps.updatepath!()
# Install dependencies when applicable.
Expand Down Expand Up @@ -447,7 +452,7 @@ function deploydocs(;
git_push(
root, temp, repo;
branch=branch, dirname=dirname, target=target,
sha=sha, deploy_config=deploy_config,
sha=sha, deploy_config=deploy_config, subfolder=subfolder,
devurl=devurl, versions=versions, forcepush=forcepush,
)
end
Expand All @@ -458,18 +463,17 @@ end
"""
git_push(
root, tmp, repo;
branch="gh-pages", dirname="", target="site", sha="", devurl="dev", deploy_config
branch="gh-pages", dirname="", target="site", sha="", devurl="dev",
deploy_config, folder,
)
Handles pushing changes to the remote documentation branch.
The documentation are placed in the `devurl` folder
(if `git_tag(deploy_config)` returns `nothing`) or the version folder
corresponding to `git_tag(deploy_config)`, e.g. a `vX.Y.Z` directory.
The documentation are placed in the folder specified by `subfolder`.
"""
function git_push(
root, temp, repo;
branch="gh-pages", dirname="", target="site", sha="", devurl="dev",
versions, forcepush=false, deploy_config,
versions, forcepush=false, deploy_config, subfolder,
)
dirname = isempty(dirname) ? temp : joinpath(temp, dirname)
isdir(dirname) || mkpath(dirname)
Expand Down Expand Up @@ -506,17 +510,10 @@ function git_push(
run(`git commit --allow-empty -m "Initial empty commit for docs"`)
end

# Copy docs to `devurl`, or `stable`, `<release>`, and `<version>` directories.
tag = git_tag(deploy_config)
if tag === nothing
devurl_dir = joinpath(dirname, devurl)
gitrm_copy(target_dir, devurl_dir)
Writers.HTMLWriter.generate_siteinfo_file(devurl_dir, devurl)
else
tagged_dir = joinpath(dirname, tag)
gitrm_copy(target_dir, tagged_dir)
Writers.HTMLWriter.generate_siteinfo_file(tagged_dir, tag)
end
# Copy docs to `subfolder` directory.
deploy_dir = joinpath(dirname, subfolder)
gitrm_copy(target_dir, deploy_dir)
Writers.HTMLWriter.generate_siteinfo_file(deploy_dir, subfolder)

# Expand the users `versions` vector
entries, symlinks = Writers.HTMLWriter.expand_versions(dirname, versions)
Expand Down Expand Up @@ -590,7 +587,7 @@ function git_push(
cd(git_commands, temp)
end
catch e
@error "Failed to push:" e
@error "Failed to push:" exception=(e, catch_backtrace())
finally
# Remove the unencrypted private key.
isfile(keyfile) && rm(keyfile)
Expand All @@ -601,7 +598,7 @@ function git_push(
try
cd(git_commands, temp)
catch e
@error "Failed to push:" e
@error "Failed to push:" exception=(e, catch_backtrace())
end
end
end
Expand All @@ -627,6 +624,9 @@ first, `git add -A` will not detect case changes in filenames.
function gitrm_copy(src, dst)
# --ignore-unmatch so that we wouldn't get errors if dst does not exist
run(`git rm -rf --ignore-unmatch $(dst)`)
# git rm also removed parent directories
# if they are empty so need to mkpath after
mkpath(dst)
cp(src, dst; force=true)
end

Expand Down Expand Up @@ -747,8 +747,7 @@ function doctest(
)
true
catch err
@error "Doctesting failed"
showerror(stdout, err, catch_backtrace())
@error "Doctesting failed" exception=(err, catch_backtrace())
false
finally
rm(dir; recursive=true)
Expand Down
189 changes: 123 additions & 66 deletions src/deployconfig.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,20 @@ function documenter_key(::DeployConfig)
end

"""
Documenter.git_tag(cfg::DeployConfig)
Documenter.deploy_folder(cfg::DeployConfig; repo, devbranch, push_preview, devurl, kwargs...)
Return the git tag of the build. If the build is not for a tag, return `nothing`.
Return the folder where the documentation should be deployed to, or `nothing`
if the current build should not deploy.
This function is called with the `repo`, `devbranch`, `push_preview` and `devurl`
arguments from [`deploydocs`](@ref).
This function determines the subfolder where the built docs are deployed.
Either a folder named as the tag (e.g. `vX.Y.Z`) or `devurl`
(see [`deploydocs`](@ref)) for non-tag builds.
!!! note
Implementations of this functions should accept trailing `kwargs...` for
compatibility with future Documenter releases which may pass additional
keyword arguments.
"""
git_tag(::DeployConfig) = nothing

"""
Documenter.should_deploy(cfg::DeployConfig; repo=repo, devbranch=devbranch)
Return `true` if the current build should deploy, and `false` otherwise.
This function is called with the `repo` and `devbranch` arguments from
[`deploydocs`](@ref).
"""
should_deploy(::DeployConfig; kwargs...) = false
should_deploy(::Nothing; kwargs...) = false # when auto-detection fails
deploy_folder(::DeployConfig; kwargs...) = nothing
deploy_folder(::Nothing; kwargs...) = nothing # when auto-detection fails

@enum AuthenticationMethod SSH HTTPS

Expand All @@ -61,6 +56,8 @@ This method must be supported by configs that push with HTTPS, see
"""
function authenticated_repo_url end

marker(x) = x ? "" : ""

#############
# Travis CI #
#############
Expand Down Expand Up @@ -117,36 +114,66 @@ function Travis()
end

# Check criteria for deployment
function should_deploy(cfg::Travis; repo, devbranch, kwargs...)
function deploy_folder(cfg::Travis; repo, devbranch, push_preview, devurl, kwargs...)
io = IOBuffer()
all_ok = true
## Determine build type; release, devbranch or preview
if cfg.travis_pull_request != "false"
build_type = :preview
elseif !isempty(cfg.travis_tag)
build_type = :release
else
build_type = :devbranch
end
println(io, "Deployment criteria for deploying $(build_type) build from Travis:")
## The deploydocs' repo should match TRAVIS_REPO_SLUG
repo_ok = occursin(cfg.travis_repo_slug, repo)
## Do not deploy for PRs
pr_ok = cfg.travis_pull_request == "false"
## If a tag exist it should be a valid VersionNumber
tag_ok = isempty(cfg.travis_tag) || occursin(Base.VERSION_REGEX, cfg.travis_tag)
## If no tag exists deploydocs' devbranch should match TRAVIS_BRANCH
branch_ok = !isempty(cfg.travis_tag) || cfg.travis_branch == devbranch
all_ok &= repo_ok
println(io, "- $(marker(repo_ok)) ENV[\"TRAVIS_REPO_SLUG\"]=\"$(cfg.travis_repo_slug)\" occurs in repo=\"$(repo)\"")
if build_type === :release
## Do not deploy for PRs
pr_ok = cfg.travis_pull_request == "false"
println(io, "- $(marker(pr_ok)) ENV[\"TRAVIS_PULL_REQUEST\"]=\"$(cfg.travis_pull_request)\" is \"false\"")
all_ok &= pr_ok
## If a tag exist it should be a valid VersionNumber
tag_ok = occursin(Base.VERSION_REGEX, cfg.travis_tag)
all_ok &= tag_ok
println(io, "- $(marker(tag_ok)) ENV[\"TRAVIS_TAG\"] contains a valid VersionNumber")
## Deploy to folder according to the tag
subfolder = cfg.travis_tag
elseif build_type === :devbranch
## Do not deploy for PRs
pr_ok = cfg.travis_pull_request == "false"
println(io, "- $(marker(pr_ok)) ENV[\"TRAVIS_PULL_REQUEST\"]=\"$(cfg.travis_pull_request)\" is \"false\"")
all_ok &= pr_ok
## deploydocs' devbranch should match TRAVIS_BRANCH
branch_ok = !isempty(cfg.travis_tag) || cfg.travis_branch == devbranch
all_ok &= branch_ok
println(io, "- $(marker(branch_ok)) ENV[\"TRAVIS_BRANCH\"] matches devbranch=\"$(devbranch)\"")
## Deploy to deploydocs devurl kwarg
subfolder = devurl
else # build_type === :preview
pr_number = tryparse(Int, cfg.travis_pull_request)
pr_ok = pr_number !== nothing
all_ok &= pr_ok
println(io, "- $(marker(pr_ok)) ENV[\"TRAVIS_PULL_REQUEST\"]=\"$(cfg.travis_pull_request)\" is a number")
btype_ok = push_preview
all_ok &= btype_ok
println(io, "- $(marker(btype_ok)) `push_preview` keyword argument to deploydocs is `true`")
## deploy to previews/PR
subfolder = "previews/PR$(something(pr_number, 0))"
end
## DOCUMENTER_KEY should exist (just check here and extract the value later)
key_ok = haskey(ENV, "DOCUMENTER_KEY")
all_ok &= key_ok
println(io, "- $(marker(key_ok)) ENV[\"DOCUMENTER_KEY\"] exists")
## Cron jobs should not deploy
type_ok = cfg.travis_event_type != "cron"
all_ok = repo_ok && pr_ok && tag_ok && branch_ok && key_ok && type_ok
marker(x) = x ? "" : ""
@info """Deployment criteria for deploying with Travis:
- $(marker(repo_ok)) ENV["TRAVIS_REPO_SLUG"]="$(cfg.travis_repo_slug)" occurs in repo="$(repo)"
- $(marker(pr_ok)) ENV["TRAVIS_PULL_REQUEST"]="$(cfg.travis_pull_request)" is "false"
- $(marker(tag_ok)) ENV["TRAVIS_TAG"]="$(cfg.travis_tag)" is (i) empty or (ii) a valid VersionNumber
- $(marker(branch_ok)) ENV["TRAVIS_BRANCH"]="$(cfg.travis_branch)" matches devbranch="$(devbranch)" (if tag is empty)
- $(marker(key_ok)) ENV["DOCUMENTER_KEY"] exists
- $(marker(type_ok)) ENV["TRAVIS_EVENT_TYPE"]="$(cfg.travis_event_type)" is not "cron"
Deploying: $(marker(all_ok))
"""
return all_ok
end

# Obtain git tag for the build
function git_tag(cfg::Travis)
isempty(cfg.travis_tag) ? nothing : cfg.travis_tag
all_ok &= type_ok
println(io, "- $(marker(type_ok)) ENV[\"TRAVIS_EVENT_TYPE\"]=\"$(cfg.travis_event_type)\" is not \"cron\"")
print(io, "Deploying: $(marker(all_ok))")
@info String(take!(io))
return all_ok ? subfolder : nothing
end


Expand Down Expand Up @@ -191,45 +218,75 @@ function GitHubActions()
end

# Check criteria for deployment
function should_deploy(cfg::GitHubActions; repo, devbranch, kwargs...)
function deploy_folder(cfg::GitHubActions; repo, devbranch, push_preview, devurl, kwargs...)
io = IOBuffer()
all_ok = true
## Determine build type
if cfg.github_event_name == "pull_request"
build_type = :preview
elseif occursin(r"^refs\/tags\/(.*)$", cfg.github_ref)
build_type = :release
else
build_type = :devbranch
end
println(io, "Deployment criteria for deploying $(build_type) build from GitHub Actions:")
## The deploydocs' repo should match GITHUB_REPOSITORY
repo_ok = occursin(cfg.github_repository, repo)
## Do not deploy for PRs
pr_ok = cfg.github_event_name == "push"
## If a tag exist it should be a valid VersionNumber
m = match(r"^refs/tags/(.*)$", cfg.github_ref)
tag_ok = m === nothing ? false : occursin(Base.VERSION_REGEX, String(m.captures[1]))
## If no tag exists deploydocs' devbranch should match the current branch
m = match(r"^refs/heads/(.*)$", cfg.github_ref)
branch_ok = m === nothing ? false : String(m.captures[1]) == devbranch
all_ok &= repo_ok
println(io, "- $(marker(repo_ok)) ENV[\"GITHUB_REPOSITORY\"]=\"$(cfg.github_repository)\" occurs in repo=\"$(repo)\"")
if build_type === :release
## Do not deploy for PRs
event_ok = cfg.github_event_name == "push"
all_ok &= event_ok
println(io, "- $(marker(event_ok)) ENV[\"GITHUB_EVENT_NAME\"]=\"$(cfg.github_event_name)\" is \"push\"")
## If a tag exist it should be a valid VersionNumber
m = match(r"^refs\/tags\/(.*)$", cfg.github_ref)
tag_ok = m === nothing ? false : occursin(Base.VERSION_REGEX, String(m.captures[1]))
all_ok &= tag_ok
println(io, "- $(marker(tag_ok)) ENV[\"GITHUB_REF\"]=\"$(cfg.github_ref)\" contains a valid VersionNumber")
## Deploy to folder according to the tag
subfolder = m === nothing ? nothing : String(m.captures[1])
elseif build_type === :devbranch
## Do not deploy for PRs
event_ok = cfg.github_event_name == "push"
all_ok &= event_ok
println(io, "- $(marker(event_ok)) ENV[\"GITHUB_EVENT_NAME\"]=\"$(cfg.github_event_name)\" is \"push\"")
## deploydocs' devbranch should match the current branch
m = match(r"^refs\/heads\/(.*)$", cfg.github_ref)
branch_ok = m === nothing ? false : String(m.captures[1]) == devbranch
all_ok &= branch_ok
println(io, "- $(marker(branch_ok)) ENV[\"GITHUB_REF\"] matches devbranch=\"$(devbranch)\"")
## Deploy to deploydocs devurl kwarg
subfolder = devurl
else # build_type === :preview
m = match(r"refs\/pull\/(\d+)\/merge", cfg.github_ref)
pr_number = tryparse(Int, m === nothing ? "" : m.captures[1])
pr_ok = pr_number !== nothing
all_ok &= pr_ok
println(io, "- $(marker(pr_ok)) ENV[\"GITHUB_REF\"] corresponds to a PR number")
btype_ok = push_preview
all_ok &= btype_ok
println(io, "- $(marker(btype_ok)) `push_preview` keyword argument to deploydocs is `true`")
## deploydocs to previews/PR
subfolder = "previews/PR$(something(pr_number, 0))"
end
## GITHUB_ACTOR should exist (just check here and extract the value later)
actor_ok = haskey(ENV, "GITHUB_ACTOR")
all_ok &= actor_ok
println(io, "- $(marker(actor_ok)) ENV[\"GITHUB_ACTOR\"] exists")
## GITHUB_TOKEN should exist (just check here and extract the value later)
token_ok = haskey(ENV, "GITHUB_TOKEN")
all_ok = repo_ok && pr_ok && (tag_ok || branch_ok) && actor_ok && token_ok
marker(x) = x ? "" : ""
@info """Deployment criteria for deploying with GitHub Actions:
- $(marker(repo_ok)) ENV["GITHUB_REPOSITORY"]="$(cfg.github_repository)" occurs in repo="$(repo)"
- $(marker(pr_ok)) ENV["GITHUB_EVENT_NAME"]="$(cfg.github_event_name)" is "push"
- $(marker(tag_ok || branch_ok)) ENV["GITHUB_REF"]="$(cfg.github_ref)" corresponds to a tag or matches devbranch="$(devbranch)"
- $(marker(actor_ok)) ENV["GITHUB_ACTOR"] exists
- $(marker(token_ok)) ENV["GITHUB_TOKEN"] exists
Deploying: $(marker(all_ok))
"""
return all_ok
all_ok &= token_ok
println(io, "- $(marker(token_ok)) ENV[\"GITHUB_TOKEN\"] exists")
print(io, "Deploying: $(marker(all_ok))")
return all_ok ? subfolder : nothing
end

authentication_method(::GitHubActions) = HTTPS
function authenticated_repo_url(cfg::GitHubActions)
return "https://$(ENV["GITHUB_ACTOR"]):$(ENV["GITHUB_TOKEN"])@github.com/$(cfg.github_repository).git"
end

# Obtain git tag for the build
function git_tag(cfg::GitHubActions)
m = match(r"^refs/tags/(.*)$", cfg.github_ref)
return m === nothing ? nothing : String(m.captures[1])
end


##################
# Auto-detection #
Expand Down
Loading

0 comments on commit 40c0f53

Please sign in to comment.