Skip to content

Commit 6c2012b

Browse files
hadleyjennybc
andauthored
Reduce number of manual git release steps (#1746)
* Reduce number of manual release steps * `use_release_issue()` checks default branch and pulls automatically. * `use_github_release()` automatically pushes, if safe. * `use_github_release()` just creates the release. * `use_version()` gains `push` argument. Fixes #1385. Closes #1678. * Restore initial git pull * Extract out git_push_first() * Apply suggestions from code review Co-authored-by: Jennifer (Jenny) Bryan <jenny.f.bryan@gmail.com> * WS * Re-document * Tweak argument order * Make some cli helper wrappers * Don't mess with function order * Update NEWS * Tweak word * Tweak comment * Polish docs * Don't mention release() --------- Co-authored-by: Jennifer (Jenny) Bryan <jenny.f.bryan@gmail.com>
1 parent cb4285b commit 6c2012b

File tree

11 files changed

+155
-87
lines changed

11 files changed

+155
-87
lines changed

NEWS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# usethis (development version)
22

3+
* `use_version()` gains a `push` argument to optionally push the result after
4+
committing. This is used to eliminate a manual step from the
5+
`use_release_issue()` checklist (#1385).
6+
7+
* `use_github_release()` now automatically pushes to GitHub (if safe) (#1385)
8+
and automatically publishes the release, rather than requiring you to edit
9+
and publish the draft.
10+
311
* `use_tidy_logo()` is a new function that calls `use_logo()` on the appropriate
412
hex sticker PNG file at <https://github.com/rstudio/hex-stickers> (@ateucher,
513
#1871).

R/github.R

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,26 +158,17 @@ use_github <- function(organisation = NULL,
158158

159159
if (is_package()) {
160160
# we tryCatch(), because we can't afford any failure here to result in not
161-
# making the first push and configuring default branch
161+
# doing the first push and configuring the default branch
162162
# such an incomplete setup is hard to diagnose / repair post hoc
163163
tryCatch(
164164
use_github_links(),
165165
error = function(e) NULL
166166
)
167167
}
168168

169-
repo <- git_repo()
170-
remref <- glue("origin/{default_branch}")
171-
ui_done("
172-
Pushing {ui_value(default_branch)} branch to GitHub and setting \\
173-
{ui_value(remref)} as upstream branch")
174-
gert::git_push(
175-
remote = "origin",
176-
set_upstream = TRUE,
177-
repo = repo,
178-
verbose = FALSE
179-
)
169+
git_push_first(default_branch, "origin")
180170

171+
repo <- git_repo()
181172
gbl <- gert::git_branch_list(local = TRUE, repo = repo)
182173
if (nrow(gbl) > 1) {
183174
ui_done("

R/pr.R

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -383,22 +383,15 @@ pr_push <- function() {
383383
)
384384
title <- glue("Which repo do you want to push to?")
385385
choice <- utils::menu(choices, graphics = FALSE, title = title)
386-
remote <- names(choices)[[choice]]
386+
remote <- names(choices)[[choice]]
387387
} else {
388388
remote <- "origin"
389389
}
390-
ui_done("
391-
Pushing local {ui_value(branch)} branch to {ui_value(remote)} remote.")
392-
gert::git_push(remote = remote, verbose = FALSE, repo = repo)
390+
391+
git_push_first(branch, remote)
393392
} else {
394393
check_branch_pulled(use = "pr_pull()")
395-
ui_done("Pushing local {ui_value(branch)} branch to {ui_value(remref)}.")
396-
gert::git_push(
397-
remote = remref_remote(remref),
398-
refspec = glue("refs/heads/{branch}:refs/heads/{remref_branch(remref)}"),
399-
verbose = FALSE,
400-
repo = repo
401-
)
394+
git_push(branch, remref)
402395
}
403396

404397
# Prompt to create PR if does not exist yet

R/proj-desc.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ proj_desc_create <- function(name, fields = list(), roxygen = TRUE) {
2222
write_over(proj_path("DESCRIPTION"), read_utf8(tf))
2323

2424
# explicit check of "usethis.quiet" since I'm not doing the printing
25-
if (!getOption("usethis.quiet", default = FALSE)) {
25+
if (!is_quiet()) {
2626
desc$print()
2727
}
2828
}

R/release.R

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,9 @@ release_checklist <- function(version, on_cran) {
128128
"Wait for CRAN...",
129129
"",
130130
todo("Accepted :tada:"),
131-
todo("`git push`"),
132131
todo("`usethis::use_github_release()`"),
133-
todo("`usethis::use_dev_version()`"),
134-
todo("`git push`"),
132+
todo("`usethis::use_dev_version(push = TRUE)`"),
133+
todo("`usethis::use_news_md()`", !has_news),
135134
todo("Finish blog post", type != "patch"),
136135
todo("Tweet", type != "patch"),
137136
todo("Add link to blog post in pkgdown news menu", type != "patch")
@@ -207,28 +206,27 @@ release_type <- function(version) {
207206
}
208207
}
209208

210-
#' Draft a GitHub release
209+
#' Publish a GitHub release
211210
#'
212211
#' @description
213-
#' Creates a __draft__ GitHub release for the current package. Once you are
214-
#' satisfied that it is correct, you will need to publish the release from
215-
#' GitHub. The key pieces of info are which commit / SHA to tag, the associated
216-
#' package version, and the relevant NEWS entries.
217-
#'
218-
#' If you use `devtools::release()` or `devtools::submit_cran()` to submit to
219-
#' CRAN, information about the submitted state is captured in a CRAN-SUBMISSION
220-
#' or CRAN-RELEASE file. `use_github_release()` uses this info to populate the
221-
#' draft GitHub release and, after success, deletes the CRAN-SUBMISSION or
222-
#' CRAN-RELEASE file.
212+
#' Pushes the current branch (if safe) then publishes a GitHub release for the
213+
#' latest CRAN submission.
223214
#'
224-
#' In the absence of such a file, we must fall back to assuming the current
225-
#' state (SHA of `HEAD`, package version, NEWS) is the submitted state.
215+
#' If you use [devtools::submit_cran()] to submit to CRAN, information about the
216+
#' submitted state is captured in a `CRAN-SUBMISSION` file.
217+
#' `use_github_release()` uses this info to populate the GitHub release notes
218+
#' and, after success, deletes the file. In the absence of such a file, we
219+
#' assume that current state (SHA of `HEAD`, package version, NEWS) is the
220+
#' submitted state.
226221
#'
227222
#' @param host,auth_token `r lifecycle::badge("deprecated")`: No longer
228223
#' consulted now that usethis allows the gh package to lookup a token based on
229224
#' a URL determined from the current project's GitHub remotes.
225+
#' @param publish If `TRUE`, publishes a release. If `FALSE`, creates a draft
226+
#' release.
230227
#' @export
231-
use_github_release <- function(host = deprecated(),
228+
use_github_release <- function(publish = TRUE,
229+
host = deprecated(),
232230
auth_token = deprecated()) {
233231
check_is_package("use_github_release()")
234232
if (lifecycle::is_present(host)) {
@@ -239,7 +237,7 @@ use_github_release <- function(host = deprecated(),
239237
}
240238

241239
tr <- target_repo(github_get = TRUE, ok_configs = c("ours", "fork"))
242-
check_can_push(tr = tr, "to draft a release")
240+
check_can_push(tr = tr, "to create a release")
243241

244242
dat <- get_release_data(tr)
245243
release_name <- glue("{dat$Package} {dat$Version}")
@@ -248,26 +246,33 @@ use_github_release <- function(host = deprecated(),
248246
kv_line("Tag name", tag_name)
249247
kv_line("SHA", dat$SHA)
250248

249+
if (git_can_push()) {
250+
git_push()
251+
}
251252
check_github_has_SHA(SHA = dat$SHA, tr = tr)
252253

253254
on_cran <- !is.null(cran_version())
254255
news <- get_release_news(SHA = dat$SHA, tr = tr, on_cran = on_cran)
255256

256257
gh <- gh_tr(tr)
258+
259+
ui_cli_inform("Publishing {tag_name} release to GitHub")
257260
release <- gh(
258261
"POST /repos/{owner}/{repo}/releases",
259-
name = release_name, tag_name = tag_name,
260-
target_commitish = dat$SHA, body = news, draft = TRUE
262+
name = release_name,
263+
tag_name = tag_name,
264+
target_commitish = dat$SHA,
265+
body = news,
266+
draft = !publish
261267
)
268+
ui_cli_inform("Release at {.url {release$html_url}}")
262269

263270
if (!is.null(dat$file)) {
264-
ui_done("{ui_path(dat$file)} deleted")
271+
ui_cli_inform("Deleting {.path {dat$file}}")
265272
file_delete(dat$file)
266273
}
267274

268-
Sys.sleep(1)
269-
view_url(release$html_url)
270-
ui_todo("Publish the release via \"Edit draft\" > \"Publish release\"")
275+
invisible()
271276
}
272277

273278
get_release_data <- function(tr = target_repo(github_get = TRUE)) {

R/ui.R

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,17 @@ ui_bullet <- function(x, bullet = cli::symbol$bullet) {
299299

300300
# All UI output must eventually go through ui_inform() so that it
301301
# can be quieted with 'usethis.quiet' when needed.
302-
ui_inform <- function(..., quiet = getOption("usethis.quiet", default = FALSE)) {
303-
if (!quiet) {
302+
ui_inform <- function(...) {
303+
if (!is_quiet()) {
304304
inform(paste0(...))
305305
}
306-
307306
invisible()
308307
}
309308

309+
is_quiet <- function() {
310+
isTRUE(getOption("usethis.quiet", default = FALSE))
311+
}
312+
310313
# Sitrep helpers ---------------------------------------------------------------
311314

312315
hd_line <- function(name) {
@@ -318,3 +321,13 @@ kv_line <- function(key, value, .envir = parent.frame()) {
318321
key <- glue(key, .envir = .envir)
319322
ui_inform(glue("{cli::symbol$bullet} {key}: {value}"))
320323
}
324+
325+
326+
# cli wrappers ------------------------------------------------------------
327+
328+
ui_cli_inform <- function(..., .envir = parent.frame()) {
329+
if (!is_quiet()) {
330+
cli::cli_inform(..., .envir = .envir)
331+
}
332+
invisible()
333+
}

R/utils-git.R

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ git_status <- function(untracked) {
127127
}
128128

129129
# Commit -----------------------------------------------------------------------
130-
git_ask_commit <- function(message, untracked, paths = NULL) {
130+
git_ask_commit <- function(message, untracked, push = FALSE, paths = NULL) {
131131
if (!is_interactive() || !uses_git()) {
132132
return(invisible())
133133
}
@@ -168,9 +168,22 @@ git_ask_commit <- function(message, untracked, paths = NULL) {
168168
paste0("* ", ui_paths)
169169
))
170170

171-
if (ui_yeah("Is it ok to commit {if (n == 1) 'it' else 'them'}?")) {
171+
# Only push if no remote & a single change
172+
push <- push && git_can_push(max_local = 1)
173+
174+
msg <- paste0(
175+
"Is it ok to commit ",
176+
if (push) "and push ",
177+
if (n == 1) 'it' else 'them',
178+
"?"
179+
)
180+
if (ui_yeah(msg)) {
172181
git_commit(paths, message)
182+
if (push) {
183+
git_push()
184+
}
173185
}
186+
174187
invisible()
175188
}
176189

@@ -317,6 +330,45 @@ git_branch_compare <- function(branch = git_branch(), remref = NULL) {
317330
list(local_only = out$ahead, remote_only = out$behind)
318331
}
319332

333+
git_can_push <- function(max_local = Inf, branch = git_branch(), remref = NULL) {
334+
remref <- remref %||% git_branch_tracking(branch)
335+
if (is.null(remref)) {
336+
return(FALSE)
337+
}
338+
comp <- git_branch_compare(branch, remref)
339+
comp$remote_only == 0 && comp$local_only <= max_local
340+
}
341+
342+
git_push <- function(branch = git_branch(), remref = NULL, verbose = TRUE) {
343+
remref <- remref %||% git_branch_tracking(branch)
344+
if (verbose) {
345+
ui_done("Pushing local {ui_value(branch)} branch to {ui_value(remref)}.")
346+
}
347+
348+
gert::git_push(
349+
remote = remref_remote(remref),
350+
refspec = glue("refs/heads/{branch}:refs/heads/{remref_branch(remref)}"),
351+
verbose = FALSE,
352+
repo = git_repo()
353+
)
354+
}
355+
356+
git_push_first <- function(branch = git_branch(), remote = "origin", verbose = TRUE) {
357+
if (verbose) {
358+
remref <- glue("{remote}/{branch}")
359+
ui_done("
360+
Pushing {ui_value(branch)} branch to GitHub and setting \\
361+
{ui_value(remref)} as upstream branch"
362+
)
363+
}
364+
gert::git_push(
365+
remote = remote,
366+
set_upstream = TRUE,
367+
verbose = FALSE,
368+
repo = git_repo()
369+
)
370+
}
371+
320372
# Checks ------------------------------------------------------------------
321373

322374
check_current_branch <- function(is = NULL, is_not = NULL,

R/version.R

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
#' ```
1515

1616
#' `use_version()` increments the "Version" field in `DESCRIPTION`, adds a new
17-
#' heading to `NEWS.md` (if it exists), and commits those changes (if package
18-
#' uses Git). It makes the same update to a line like `PKG_version = "x.y.z";`
19-
#' in `src/version.c` (if it exists).
17+
#' heading to `NEWS.md` (if it exists), commits those changes (if package uses
18+
#' Git), and optionally pushes (if safe to do so). It makes the same update to a
19+
#' line like `PKG_version = "x.y.z";` in `src/version.c` (if it exists).
2020
#'
2121

2222
#' `use_dev_version()` increments to a development version, e.g. from 1.0.0 to
@@ -46,8 +46,10 @@
4646
NULL
4747

4848
#' @rdname use_version
49+
#' @param push If `TRUE`, also attempts to push the commits to the remote
50+
#' branch.
4951
#' @export
50-
use_version <- function(which = NULL) {
52+
use_version <- function(which = NULL, push = FALSE) {
5153
if (is.null(which) && !is_interactive()) {
5254
return(invisible(FALSE))
5355
}
@@ -74,8 +76,10 @@ use_version <- function(which = NULL) {
7476
git_ask_commit(
7577
glue("Increment version number to {new_ver}"),
7678
untracked = TRUE,
79+
push = push,
7780
paths = c("DESCRIPTION", "NEWS.md", path("src", "version.c"))
7881
)
82+
7983
invisible(TRUE)
8084
}
8185

man/use_github_release.Rd

Lines changed: 17 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)