From fdf7f9a8323c427d2784b69f7d8a61200ff09a2f Mon Sep 17 00:00:00 2001 From: Andy Teucher Date: Fri, 19 Sep 2025 12:07:40 -0400 Subject: [PATCH 1/4] Add support for non-text files in use_github_file() Add mime dependency and update use_github_file() to handle both text and binary files by detecting MIME type. Fixes #1782 --- DESCRIPTION | 1 + R/use_github_file.R | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3e61801c3..db42d2e05 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -34,6 +34,7 @@ Imports: glue (>= 1.3.0), jsonlite, lifecycle (>= 1.0.0), + mime, purrr, rappdirs, rlang (>= 1.1.0), diff --git a/R/use_github_file.R b/R/use_github_file.R index 847a13613..b831bb300 100644 --- a/R/use_github_file.R +++ b/R/use_github_file.R @@ -77,37 +77,57 @@ use_github_file <- function( "v" = "Saving {.val {github_string}} to {.path {pth(save_as)}}." )) - lines <- read_github_file( + tf <- get_github_file( repo_spec = repo_spec, path = path, ref = ref, host = host ) - new <- write_over( - proj_path(save_as), - lines, - quiet = TRUE, - overwrite = overwrite - ) + + # If it's a text file, we read and write to make sure it is utf-8, + # otherwise, copy to its final destination + is_text <- grepl("^text/", mime::guess_type(tf)) + + if (is_text) { + new <- write_over( + proj_path(save_as), + read_utf8(tf), + quiet = TRUE, + overwrite = overwrite + ) + } else { + file_copy(tf, proj_path(save_as), overwrite = overwrite) + new <- file_exists(proj_path(save_as)) + } if (ignore) { use_build_ignore(save_as) } - if (open && new) { + if (is_text && open && new) { edit_file(proj_path(save_as)) } invisible(new) } -read_github_file <- function(repo_spec, path, ref = NULL, host = NULL) { +get_github_file <- function( + repo_spec, + path, + ref = NULL, + host = NULL, + envir = parent.frame() +) { # https://docs.github.com/en/rest/reference/repos#contents # https://docs.github.com/en/rest/reference/repos#if-the-content-is-a-symlink # If the requested {path} points to a symlink, and the symlink's target is a # normal file in the repository, then the API responds with the content of the # file.... - tf <- withr::local_tempfile() + tf <- withr::local_tempfile( + fileext = paste0(".", path_ext(path)), + .local_envir = envir + ) + gh::gh( "/repos/{repo_spec}/contents/{path}", repo_spec = repo_spec, @@ -117,7 +137,7 @@ read_github_file <- function(repo_spec, path, ref = NULL, host = NULL) { .destfile = tf, .accept = "application/vnd.github.v3.raw" ) - read_utf8(tf) + tf } # https://github.com/OWNER/REPO/blob/REF/path/to/some/file From 337a88f9052d8c83951f77d12c0a50baa6b98067 Mon Sep 17 00:00:00 2001 From: Andy Teucher Date: Fri, 19 Sep 2025 12:08:27 -0400 Subject: [PATCH 2/4] Replace read_github_file() with get_github_file() Need to read_utf() now since get_github_file() returns a path --- R/release.R | 5 +++-- R/use_standalone.R | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/R/release.R b/R/release.R index 89722e90e..36357a106 100644 --- a/R/release.R +++ b/R/release.R @@ -393,14 +393,15 @@ get_release_news <- function( news <- if (file_exists(news_path)) read_utf8(news_path) else NULL } else { news <- tryCatch( - read_github_file( + get_github_file( tr$repo_spec, path = "NEWS.md", ref = SHA, host = tr$api_url ), github_error = NULL - ) + ) |> + read_utf8() } if (is.null(news)) { diff --git a/R/use_standalone.R b/R/use_standalone.R index 6d13d1f9d..d1e8e3a77 100644 --- a/R/use_standalone.R +++ b/R/use_standalone.R @@ -73,7 +73,13 @@ use_standalone <- function(repo_spec, file = NULL, ref = NULL, host = NULL) { src_path <- path("R", file) dest_path <- path("R", as_standalone_dest_file(file)) - lines <- read_github_file(repo_spec, path = src_path, ref = ref, host = host) + lines <- get_github_file( + repo_spec, + path = src_path, + ref = ref, + host = host + ) |> + read_utf8() lines <- c(standalone_header(repo_spec, src_path, ref, host), lines) write_over(proj_path(dest_path), lines, overwrite = TRUE) From d3073ab018efd53528c882e3955639741a286896 Mon Sep 17 00:00:00 2001 From: Andy Teucher Date: Fri, 19 Sep 2025 12:08:43 -0400 Subject: [PATCH 3/4] Add test for use_github_file with non-text files --- tests/testthat/test-use_github_file.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/testthat/test-use_github_file.R b/tests/testthat/test-use_github_file.R index f8d0abc2d..97a7e8260 100644 --- a/tests/testthat/test-use_github_file.R +++ b/tests/testthat/test-use_github_file.R @@ -59,3 +59,13 @@ test_that("parse_file_url() errors when it should", { "https://gitlab.com/OWNER/REPO/path/to/file" )) }) + +test_that("use_github_file works with non-text files", { + create_local_project() + use_github_file( + "https://github.com/r-lib/usethis/blob/main/man/figures/logo.png", + save_as = "logo.png" + ) + + expect_proj_file("logo.png") +}) From fa3f961b42336a4b59674c02a7fc3f9fefd63754 Mon Sep 17 00:00:00 2001 From: Andy Teucher Date: Fri, 19 Sep 2025 12:12:10 -0400 Subject: [PATCH 4/4] Document binary files won't be opened in `use_github_file()` --- R/use_github_file.R | 2 ++ man/use_github_file.Rd | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/R/use_github_file.R b/R/use_github_file.R index b831bb300..08fa4a414 100644 --- a/R/use_github_file.R +++ b/R/use_github_file.R @@ -22,6 +22,8 @@ #' @param ref The name of a branch, tag, or commit. By default, the file at #' `path` will be copied from its current state in the repo's default branch. #' This is extracted from `repo_spec` when user provides a URL. +#' @param open Open the newly created file for editing, if it is a text file? Happens in RStudio, if +#' applicable, or via [utils::file.edit()] otherwise. Binary files will not be opened. #' @inheritParams use_template #' @inheritParams use_github #' @inheritParams write_over diff --git a/man/use_github_file.Rd b/man/use_github_file.Rd index 40e63b6e3..3e3095edc 100644 --- a/man/use_github_file.Rd +++ b/man/use_github_file.Rd @@ -40,8 +40,8 @@ This is extracted from \code{repo_spec} when user provides a URL.} \item{ignore}{Should the newly created file be added to \code{.Rbuildignore}?} -\item{open}{Open the newly created file for editing? Happens in RStudio, if -applicable, or via \code{\link[utils:file.edit]{utils::file.edit()}} otherwise.} +\item{open}{Open the newly created file for editing, if it is a text file? Happens in RStudio, if +applicable, or via \code{\link[utils:file.edit]{utils::file.edit()}} otherwise. Binary files will not be opened.} \item{overwrite}{Force overwrite of existing file?}