#' Update a workflowr project.
#'
#' Newer versions of workflowr sometimes make changes that need to be
#' coordinated across multiple files. After upgrading workflowr, run
#' \code{wflow_update} to make all the necessary changes.
#'
#' By default, \code{wflow_update} is run in \code{dry_run} mode so that no
#' unwanted changes are made. The log file contains the changes to each file,
#' represented with the syntax from the
#' \href{https://en.wikipedia.org/wiki/Diff_utility#Usage}{Unix diff utility}.
#' After reviewing the log file for the proposed changes, re-run the function
#' with \code{dry_run = FALSE} to implement them.
#'
#' Currently \code{wflow_update} checks for the following items:
#'
#' \itemize{
#'
#' \item Updates the shared chunks in \code{analysis/chunks.R}.
#'
#' \item Updates each R Markdown file in \code{analysis/} to use the shared
#' chunks. This is implemented with \code{\link{wflow_convert}}.
#'
#' \item Removes the "BuildType: Website" from the Rproj file. This re-builds
#' every R Markdown file everytime, so it is safer to always use
#' \code{\link{wflow_build}}.
#'
#' }
#'
#' @param dry_run logical (default: TRUE). Preview the proposed updates.
#' @param commit logical (default: TRUE). Commit the updated files (only files
#' tracked by Git are included in commit). Only executed if \code{dry_run =
#' FALSE}.
#' @param log_file character (default: NULL). A file to save the log messages.
#' If NULL, a temporary file is created.
#' @param log_open logical (default: \code{interactive()}). Should the log file
#' be opened in RStudio? This argument is ignored if the function is not run
#' from within RStudio.
#' @param project character (default: ".") By default the function assumes the
#' current working directory is within the project. If this is not true,
#' you'll need to provide the path to the project directory.
#'
#' @return A character vector of the updated files.
#'
#' @seealso \code{\link{wflow_convert}}
#'
#' @examples
#' \dontrun{
#'
#' # Preview the potential changes
#' wflow_update()
#' # Incorporate the changes
#' wflow_update(dry_run = FALSE)
#' }
#'
#' @export
wflow_update <- function(dry_run = TRUE,
commit = TRUE,
log_file = NULL,
log_open = interactive(),
project = ".") {
# Check input arguments ------------------------------------------------------
if (!(is.logical(dry_run) && length(dry_run) == 1))
stop("dry_run must be a one element logical vector. You entered: ", dry_run)
if (!(is.logical(commit) && length(commit) == 1))
stop("commit must be a one element logical vector. You entered: ", commit)
if (!(is.null(log_file) || (is.character(log_file) && length(log_file) == 1)))
stop("log_file must be NULL or a one element character vector. You entered: ",
log_file)
if (!(is.logical(log_open) && length(log_open) == 1))
stop("log_open must be a one element logical vector. You entered: ", log_open)
if (!(is.character(project) && length(project) == 1))
stop("project must be a one element character vector. You entered: ", project)
if (!dir.exists(project))
stop("project does not exist. You entered: ", project)
project <- absolute(project)
if (dry_run) {
message("Running wflow_update in dry run mode")
} else {
message("Running wflow_update")
}
# Setup and safety checks ----------------------------------------------------
p <- wflow_paths(project = project)
# Keep track of updated files
files_updated <- character()
# Access Git repo and fail early if files in staging area
if (!is.na(p$git) && commit) {
r <- git2r::repository(p$git)
status <- git2r::status(r)
if (length(status$staged) > 0) {
stop(call. = FALSE, wrap(
"You have added files to the Git staging area. Commit or unstage these
files prior to running wflow_update."))
}
}
# Start log file -------------------------------------------------------------
if (is.null(log_file)) {
log_file <- tempfile("log-wflow-update-", fileext = ".txt")
} else if (file.exists(log_file)) {
warning("Overwriting log file: ", log_file)
}
message("\nWriting log output to ", log_file, "\n")
# Start log file
cat("Log for wflow_update\n", file = log_file)
cat("\nDate:", as.character(Sys.time()), "\n",
file = log_file, append = TRUE)
# Output current version of workflowr
current_vers <- as.character(utils::packageVersion("workflowr"))
cat("\nThe current installed version of workflowr is", current_vers, "\n",
file = log_file, append = TRUE)
# Output explanation of diff
cat(diff_explained, file = log_file, append = TRUE)
if (log_open && rstudioapi::isAvailable()) {
on.exit(rstudioapi::navigateToFile(log_file))
}
# Update chunks.R ------------------------------------------------------------
# Is there a difference between between the chunks.R in this project
# and that which is currently available in the package?
chunks_current <- file.path(p$analysis, "chunks.R")
chunks_pkg <- system.file("infrastructure/analysis/chunks.R",
package = "workflowr")
diffs <- diff_file(from = chunks_current, to = chunks_pkg)
if (length(diffs) > 0) {
files_updated <- c(files_updated, chunks_current)
cat("\nChanges to analysis/chunks.R\n",
diffs, sep = "\n",
file = log_file, append = TRUE)
if (!dry_run) {
# Don't use file.copy to avoid line ending issues on Windows
chunks_pkg_lines <- readLines(chunks_pkg)
writeLines(chunks_pkg_lines, con = chunks_current)
}
} else {
cat("\nNo changes to analysis/chunks.R\n", sep = "\n",
file = log_file, append = TRUE)
}
# Update Rproj file ----------------------------------------------------------
# Remove BuildType
rproj_file <- list.files(path = p$root, pattern = "Rproj$",
full.names = TRUE)
if (length(rproj_file) != 1) {
cat("\nUnable to locate a single Rproj file\n")
} else {
rproj_lines <- readLines(rproj_file)
website_lines <- grepl("Website", rproj_lines)
if (sum(website_lines) == 0) {
cat(sprintf("\nNo changes to %s\n", rproj_file), sep = "\n",
file = log_file, append = TRUE)
} else {
files_updated <- c(files_updated, rproj_file)
rproj_tmp <- tempfile("rproj-", fileext = "Rproj")
rproj_lines_new <- rproj_lines[!website_lines]
cat(rproj_lines_new, sep = "\n", file = rproj_tmp)
rproj_diffs <- diff_file(from = rproj_file, to = rproj_tmp)
cat(sprintf("\nChanges to %s\n", rproj_file), rproj_diffs,
sep = "\n", file = log_file, append = TRUE)
if (!dry_run) {
file.copy(from = rproj_tmp, to = rproj_file, overwrite = TRUE)
}
}
}
# Gather all R Markdown analysis files ---------------------------------------
rmd_all <- list.files(path = p$analysis, pattern = "^[^_].*\\.[Rr]md$",
full.names = TRUE)
# Remove index.Rmd, about.Rmd, and license.Rmd
rmd_remove <- file.path(p$analysis,
c("index.Rmd", "about.Rmd", "license.Rmd"))
rmd_to_convert <- setdiff(rmd_all, rmd_remove)
# Remove any files which are untracked or have staged (or staged) changes
if (!is.na(p$git)) {
r <- git2r::repository(p$git)
status <- git2r::status(r, ignored = TRUE)
git_all <- unlist(status)
git_rmd <- git_all[grepl("[Rd]md$", git_all)]
if (length(git_rmd) > 0) {
rmd_to_convert <- setdiff(rmd_to_convert, git_rmd)
cat("", strwrap(
"The following R Markdown files are not converted because they are untracked
or have staged (or unstaged) changes. To convert, first commit the
changes and then re-run `wflow_update`:"
), "", git_rmd, sep = "\n", file = log_file, append = TRUE)
}
}
# Convert Rmd files ----------------------------------------------------------
# Attempt conversion one-by-one so that errors do not break it.
for (rmd in rmd_to_convert) {
rmd_result <- tryCatch(
rmd_diffs <- wflow_convert(rmd, dry_run = dry_run, verbose = FALSE),
error = function(e) "error")
if (rmd_result == "error") {
cat("\nUnable to convert ", rmd, ". Please inspect manually.\n",
sep = "", file = log_file, append = TRUE)
} else if (length(rmd_diffs[[1]]) > 0) {
files_updated <- c(files_updated, rmd)
cat(sprintf("\nChanges to %s:\n", rmd),
rmd_diffs[[1]], sep = "\n", file = log_file, append = TRUE)
} else {
cat("\nNo changes to", rmd, "\n", file = log_file, append = TRUE)
}
}
if (length(files_updated) > 0) {
cat("\nList of updated files:\n", files_updated, sep = "\n",
file = log_file, append = TRUE)
} else {
cat("\nNo updated files\n", sep = "\n",
file = log_file, append = TRUE)
}
# Commit updated files (tracked files only) ----------------------------------
if (!dry_run && commit && length(files_updated) > 0 && !is.na(p$git)) {
cat("\nAttempting to commit changes\n",
file = log_file, append = TRUE)
# Remove any untracked Rmd files
s <- wflow_status(project = project)
files_untracked <- rownames(s$status[!s$status[, "tracked"], ])
files_to_commit <- setdiff(files_updated, files_untracked)
git2r::add(r, path = files_to_commit)
status <- git2r::status(r)
if (length(unlist(status$staged)) > 0) {
git2r::commit(r, message = sprintf(
"Update workflowr project with wflow_update (version %s).",
current_vers))
last_commit <- git2r::commits(r)[[1]]
cat("Changes committed",
utils::capture.output(methods::show(last_commit)),
sep = "\n\n", file = log_file, append = TRUE)
} else {
cat("\nUnable to commit changes for unknown reason.",
"Manual intervention required.\n",
file = log_file, append = TRUE)
}
}
return(invisible(files_updated))
}
diff_explained <- paste(strwrap(
"\n
Explanation of diff output: The changes to the files are displayed using the
notation from the diff command-line utility. A \">\" indicates an inserted
line, and a \"<\" indicates a deleted line.
https://en.wikipedia.org/wiki/Diff_utility#Usage
\n
"
), collapse = "\n")
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.