Skip to contents

Setup a git worktree for concurrent manipulation of a separate branch

Usage

git_worktree_setup(
  path = ".",
  dest_dir,
  branch = "gh-pages",
  remote = "origin",
  throwaway = FALSE
)

Arguments

path

path to the repository

dest_dir

path to the destination directory to contain the work tree

branch

the branch associated with the work tree (default: gh-pages)

remote

the remote name (default: origin)

throwaway

if TRUE, the worktree created is in a detached HEAD state from from the remote branch and will not create a new branch in your repository. Defaults to FALSE, which will create the branch from upstream.

Value

an expression() that calls git worktree remove on the worktree when evaluated.

Details

This function is used in continuous integration settings where we want to push derived outputs to non-main branches in our repository. We use this to populate the markdown and HTML outputs from the lesson so that we don't have to rebuild the lesson from scratch every time.

The logic behind this looks like

worktree setup
[IF BRANCH DOES NOT EXIST]
  git checkout --orphan <branch>
  git rm -rf --quiet .
  git commit --allow-empty -m
  git push remote HEAD:<branch>
  git checkout -
git fetch <remote> +refs/heads/<branch>:refs/remotes/<remote>/<branch>
git worktree add --track -B <branch> /path/to/dir <remote>/<branch>

Note

this internal function has been modified from the logic in pkgdown::deploy_to_branch(), by Hadley Wickham.

Examples

run_ok <- sandpaper:::has_git() &&
  requireNamespace("withr", quietly = TRUE) &&
  rmarkdown::pandoc_available("2.11")

# Use Worktrees to deploy a lesson -----------------------------------------
# This example is a bit inovlved, but it is effectively what we do inside of
# the `ci_deploy()` function (after setting up the lesson). 
# 
# The setup phase will create a new lesson and a corresponding remote (self
# contained, no GitHub authentication required). 
# 
# The worktrees will be created for both the markdown and HTML outputs on the
# branches "md-outputs" and "gh-pages", respectively. 
# 
# After the worktrees are created, we will build the lesson into the
# worktrees and display the output of `git_status()` for each of the three
# branches: "main", "md-outputs", and "gh-pages"
# 
# During the clean up phase, the output of `git_worktree_setup()` is 
# evaluated
if (run_ok) {
cli::cli_h1("Set up")
cli::cli_h2("Create Lesson")
restore_fixture <- sandpaper:::create_test_lesson()
res <- getOption("sandpaper.test_fixture")
sandpaper:::check_git_user(res)
cli::cli_h2("Create Remote")
rmt <- fs::file_temp(pattern = "REMOTE-")
sandpaper:::setup_local_remote(repo = res, remote = rmt, verbose = FALSE)
cli::cli_h2("Create Worktrees")
db <- sandpaper:::git_worktree_setup(res, fs::path(res, "site", "built"), 
  branch = "md-outputs", remote = "sandpaper-local"
)
ds <- sandpaper:::git_worktree_setup(res, fs::path(res, "site", "docs"), 
  branch = "gh-pages", remote = "sandpaper-local"
)
cli::cli_h1("Build Lesson into worktrees")
build_lesson(res, quiet = TRUE, preview = FALSE)
cli::cli_h2("git status: {gert::git_branch(repo = res)}")
print(gert::git_status(repo = res))
cli::cli_h2('git status: {gert::git_branch(repo = fs::path(res, "site", "built"))}')
print(gert::git_status(repo = fs::path(res, "site", "built")))
cli::cli_h2('git status: {gert::git_branch(repo = fs::path(res, "site", "docs"))}')
print(gert::git_status(repo = fs::path(res, "site", "docs")))
cli::cli_h1("Clean Up")
cli::cli_alert_info("object db is an expression that evaluates to {.code {db}}")
eval(db)
cli::cli_alert_info("object ds is an expression that evaluates to {.code {ds}}")
eval(ds)
sandpaper:::remove_local_remote(repo = res)
sandpaper:::reset_git_user(res)
# remove the test fixture and report
tryCatch(fs::dir_delete(res), error = function() FALSE)
}
#> 
#> ── Set up ──────────────────────────────────────────────────────────────
#> 
#> ── Create Lesson ──
#> 
#> → Searching for and installing available dependencies
#> * Discovering package dependencies ... Done!
#> * Copying packages into the library ... Done!
#> → Recording changes in lockfile
#> The following package(s) will be updated in the lockfile:
#> 
#> # RSPM ===============================
#> - R6          [* -> 2.5.1]
#> - base64enc   [* -> 0.1-3]
#> - bslib       [* -> 0.4.2]
#> - cachem      [* -> 1.0.6]
#> - cli         [* -> 3.5.0]
#> - digest      [* -> 0.6.31]
#> - ellipsis    [* -> 0.3.2]
#> - evaluate    [* -> 0.19]
#> - fastmap     [* -> 1.1.0]
#> - fs          [* -> 1.5.2]
#> - glue        [* -> 1.6.2]
#> - highr       [* -> 0.10]
#> - htmltools   [* -> 0.5.4]
#> - jquerylib   [* -> 0.1.4]
#> - jsonlite    [* -> 1.8.4]
#> - knitr       [* -> 1.41]
#> - lifecycle   [* -> 1.0.3]
#> - magrittr    [* -> 2.0.3]
#> - memoise     [* -> 2.0.1]
#> - mime        [* -> 0.12]
#> - rappdirs    [* -> 0.3.3]
#> - renv        [* -> 0.16.0]
#> - rlang       [* -> 1.0.6]
#> - rmarkdown   [* -> 2.19]
#> - sass        [* -> 0.4.4]
#> - stringi     [* -> 1.7.8]
#> - stringr     [* -> 1.5.0]
#> - tinytex     [* -> 0.43]
#> - vctrs       [* -> 0.5.1]
#> - xfun        [* -> 0.36]
#> - yaml        [* -> 2.3.6]
#> 
#> The version of R recorded in the lockfile will be updated:
#> - R           [*] -> [4.2.2]
#> 
#> * Lockfile written to '/tmp/RtmpbkynXD/file217c47c0035c/lesson-example/renv/profiles/lesson-requirements/renv.lock'.
#> 
#> ── Create Remote ──
#> 
#> ── Create Worktrees ──
#> 
#> ::group::Create New Branch
#> Running git checkout --orphan md-outputs
#> Switched to a new branch 'md-outputs'
#> Running git rm -rf --quiet .
#> Running git commit --allow-empty -m 'Initializing md-outputs branch'
#> [md-outputs (root-commit) 50451c5] Initializing md-outputs branch
#> Running git push sandpaper-local 'HEAD:md-outputs'
#> To /tmp/RtmpbkynXD/REMOTE-217c33277b5c
#>  * [new branch]      HEAD -> md-outputs
#> Running git checkout main
#> Your branch is up to date with 'sandpaper-local/main'.
#> Switched to branch 'main'
#> ::endgroup::
#> ::group::Fetch sandpaper-local/md-outputs
#> Running git remote set-branches sandpaper-local md-outputs
#> Running git fetch sandpaper-local md-outputs
#> From /tmp/RtmpbkynXD/REMOTE-217c33277b5c
#>  * branch            md-outputs -> FETCH_HEAD
#> Running git remote set-branches sandpaper-local '*'
#> ::endgroup::
#> ::group::Add worktree for sandpaper-local/md-outputs in site/built
#> Running git worktree add --track -B md-outputs \
#>   /tmp/RtmpbkynXD/file217c47c0035c/lesson-example/site/built \
#>   sandpaper-local/md-outputs
#> Preparing worktree (resetting branch 'md-outputs'; was at 50451c5)
#> branch 'md-outputs' set up to track 'sandpaper-local/md-outputs'.
#> HEAD is now at 50451c5 Initializing md-outputs branch
#> ::endgroup::
#> ::group::Create New Branch
#> Running git checkout --orphan gh-pages
#> Switched to a new branch 'gh-pages'
#> Running git rm -rf --quiet .
#> Running git commit --allow-empty -m 'Initializing gh-pages branch'
#> [gh-pages (root-commit) 903f80b] Initializing gh-pages branch
#> Running git push sandpaper-local 'HEAD:gh-pages'
#> To /tmp/RtmpbkynXD/REMOTE-217c33277b5c
#>  * [new branch]      HEAD -> gh-pages
#> Running git checkout main
#> Your branch is up to date with 'sandpaper-local/main'.
#> Switched to branch 'main'
#> ::endgroup::
#> ::group::Fetch sandpaper-local/gh-pages
#> Running git remote set-branches sandpaper-local gh-pages
#> Running git fetch sandpaper-local gh-pages
#> From /tmp/RtmpbkynXD/REMOTE-217c33277b5c
#>  * branch            gh-pages   -> FETCH_HEAD
#> Running git remote set-branches sandpaper-local '*'
#> ::endgroup::
#> ::group::Add worktree for sandpaper-local/gh-pages in site/docs
#> Running git worktree add --track -B gh-pages \
#>   /tmp/RtmpbkynXD/file217c47c0035c/lesson-example/site/docs \
#>   sandpaper-local/gh-pages
#> Preparing worktree (resetting branch 'gh-pages'; was at 903f80b)
#> branch 'gh-pages' set up to track 'sandpaper-local/gh-pages'.
#> HEAD is now at 903f80b Initializing gh-pages branch
#> ::endgroup::
#> ── Build Lesson into worktrees ─────────────────────────────────────────
#> 
#> ── git status: main ──
#> 
#> # A tibble: 0 × 3
#> # … with 3 variables: file <chr>, status <chr>, staged <lgl>
#> ── git status: md-outputs ──
#> 
#> # A tibble: 12 × 3
#>    file                status staged
#>    <chr>               <chr>  <lgl> 
#>  1 CODE_OF_CONDUCT.md  new    FALSE 
#>  2 LICENSE.md          new    FALSE 
#>  3 config.yaml         new    FALSE 
#>  4 fig/                new    FALSE 
#>  5 index.md            new    FALSE 
#>  6 instructor-notes.md new    FALSE 
#>  7 introduction.md     new    FALSE 
#>  8 learner-profiles.md new    FALSE 
#>  9 links.md            new    FALSE 
#> 10 md5sum.txt          new    FALSE 
#> 11 renv.lock           new    FALSE 
#> 12 setup.md            new    FALSE 
#> ── git status: gh-pages ──
#> 
#> # A tibble: 34 × 3
#>    file                       status staged
#>    <chr>                      <chr>  <lgl> 
#>  1 .nojekyll                  new    FALSE 
#>  2 CODE_OF_CONDUCT.html       new    FALSE 
#>  3 LICENSE.html               new    FALSE 
#>  4 aio.html                   new    FALSE 
#>  5 android-chrome-192x192.png new    FALSE 
#>  6 android-chrome-512x512.png new    FALSE 
#>  7 apple-touch-icon.png       new    FALSE 
#>  8 assets/                    new    FALSE 
#>  9 bootstrap-toc.css          new    FALSE 
#> 10 bootstrap-toc.js           new    FALSE 
#> # … with 24 more rows
#> ── Clean Up ────────────────────────────────────────────────────────────
#>  object db is an expression that evaluates to `sandpaper:::github_worktree_remove("/tmp/RtmpbkynXD/file217c47c0035c/lesson-example/site/built", "/tmp/RtmpbkynXD/file217c47c0035c/lesson-example")`
#> Running git worktree remove --force \
#>   /tmp/RtmpbkynXD/file217c47c0035c/lesson-example/site/built
#>  object ds is an expression that evaluates to `sandpaper:::github_worktree_remove("/tmp/RtmpbkynXD/file217c47c0035c/lesson-example/site/docs", "/tmp/RtmpbkynXD/file217c47c0035c/lesson-example")`
#> Running git worktree remove --force \
#>   /tmp/RtmpbkynXD/file217c47c0035c/lesson-example/site/docs