Skip to content

Commit 66d0437

Browse files
hadleyDavisVaughanjennybc
authored
Lock and unlock branch as part of the release process (#2145)
* Lock and unlock branch as part of the release process This ensures that there's no way to accidentally forget that you're in the middle of the release process and make a change to the repo. Part of #1946 * Add to reference index * Apply suggestions from code review Co-authored-by: Davis Vaughan <[email protected]> * Redocument/update snapshot tests * Document permissions * github_lock_branch() instead of gh_lock_branch() Looking at other function names we use "github" (vs. "gh") everywhere except `gh_token_help()`. And that is arguably correct, since you could view that function as helping the gh package get a suitable token. * Make github_(un)lock_branch more consistent with other functions Also, move to different file. * More work on docs --------- Co-authored-by: Davis Vaughan <[email protected]> Co-authored-by: Jenny Bryan <[email protected]>
1 parent 53698b8 commit 66d0437

File tree

7 files changed

+139
-1
lines changed

7 files changed

+139
-1
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@ Config/usethis/last-upkeep: 2025-04-22
6363
Encoding: UTF-8
6464
Language: en-US
6565
Roxygen: list(markdown = TRUE)
66-
RoxygenNote: 7.3.2.9000
66+
RoxygenNote: 7.3.3

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export(git_protocol)
4141
export(git_remotes)
4242
export(git_sitrep)
4343
export(git_vaccinate)
44+
export(github_lock_branch)
45+
export(github_unlock_branch)
4446
export(issue_close_community)
4547
export(issue_reprex_needed)
4648
export(local_project)

R/github.R

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,101 @@ use_github_links <- function(overwrite = FALSE) {
224224
invisible()
225225
}
226226

227+
#' Lock and unlock a branch on GitHub
228+
#'
229+
#' @description
230+
#' These functions lock and unlock a branch on GitHub so that it's not possible
231+
#' for anyone to make any changes. They are used in the release checklist
232+
#' generated by [use_release_issue()]. The objective is to ensure that, once
233+
#' you've submitted your package to CRAN, you don't accidentally merge any pull
234+
#' requests or push any commits, while you are waiting for CRAN to get back to
235+
#' you. This, in turn, helps guarantee that your future Git release tag points
236+
#' to the correct commit.
237+
#'
238+
#' You must be an admin or an owner of the repo in order to lock/unlock
239+
#' a branch.
240+
#'
241+
#' @export
242+
#' @param branch The branch to lock/unlock. If not supplied, uses the
243+
#' default branch which is usually "main" or "master", as determined by
244+
#' [git_default_branch()].
245+
github_lock_branch <- function(branch = NULL) {
246+
cfg <- github_remote_config(github_get = TRUE)
247+
tr <- target_repo(cfg, role = "source", ask = FALSE)
248+
branch <- branch %||% git_default_branch_(cfg)
249+
250+
if (!isTRUE(tr$can_admin)) {
251+
ui_abort(
252+
"
253+
You don't seem to have {.field admin} permissions for the source repo
254+
{.val {tr$repo_spec}}, which is required to lock a branch."
255+
)
256+
}
257+
258+
gh <- gh_tr(tr)
259+
out <- gh(
260+
"PUT /repos/{owner}/{repo}/branches/{branch}/protection",
261+
branch = branch,
262+
# required parameters
263+
required_status_checks = NA,
264+
enforce_admins = TRUE,
265+
required_pull_request_reviews = NA,
266+
restrictions = NA,
267+
# paramater that actually does what we want
268+
lock_branch = TRUE
269+
)
270+
result <- pluck_lgl(out, "lock_branch")
271+
272+
if (isTRUE(result)) {
273+
ui_bullets(c(
274+
"v" = "Branch {.val {branch}} is locked."
275+
))
276+
}
277+
278+
invisible(out)
279+
}
280+
281+
#' @export
282+
#' @rdname github_lock_branch
283+
github_unlock_branch <- function(branch = NULL) {
284+
cfg <- github_remote_config(github_get = TRUE)
285+
tr <- target_repo(cfg, role = "source", ask = FALSE)
286+
branch <- branch %||% git_default_branch_(cfg)
287+
288+
if (!isTRUE(tr$can_admin)) {
289+
ui_abort(
290+
"
291+
You don't seem to have {.field admin} permissions for the source repo
292+
{.val {tr$repo_spec}}, which is required to unlock a branch."
293+
)
294+
}
295+
296+
gh <- gh_tr(tr)
297+
# using the endpoint to update branch protection instead of the one for
298+
# deletion, in order to get a response that helps us emit a good message
299+
# and invisibly return something useful
300+
out <- gh(
301+
"PUT /repos/{owner}/{repo}/branches/{branch}/protection",
302+
branch = branch,
303+
# required parameters
304+
required_status_checks = NA,
305+
enforce_admins = TRUE,
306+
required_pull_request_reviews = NA,
307+
restrictions = NA,
308+
# paramater that actually does what we want
309+
lock_branch = FALSE
310+
)
311+
result <- pluck_lgl(out, "lock_branch")
312+
313+
if (isFALSE(result)) {
314+
ui_bullets(c(
315+
"v" = "Branch {.val {branch}} is unlocked."
316+
))
317+
}
318+
319+
invisible(out)
320+
}
321+
227322
has_github_links <- function(target_repo = NULL) {
228323
url <- if (is.null(target_repo)) NULL else target_repo$url
229324
github_url <- github_url_from_git_remotes(url)

R/release.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,15 @@ release_checklist <- function(version, on_cran, target_repo = NULL) {
132132
"",
133133
"Submit to CRAN:",
134134
"",
135+
todo("`usethis::github_lock_branch()`"),
135136
todo("`usethis::use_version('{type}')`"),
136137
todo("`devtools::submit_cran()`"),
137138
todo("Approve email"),
138139
"",
139140
"Wait for CRAN...",
140141
"",
141142
todo("Accepted :tada:"),
143+
todo("`usethis::github_unlock_branch()`"),
142144
todo("Finish & publish blog post", type != "patch"),
143145
todo("Add link to blog post in pkgdown news menu", type != "patch"),
144146
todo("`usethis::use_github_release()`"),

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ reference:
121121
- starts_with("use_github")
122122
- git_sitrep
123123
- create_github_token
124+
- github_lock_branch
124125
- gh_token_help
125126
- git_vaccinate
126127
- use_git_config

man/github_lock_branch.Rd

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

tests/testthat/_snaps/release.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
2727
Submit to CRAN:
2828
29+
* [ ] `usethis::github_lock_branch()`
2930
* [ ] `usethis::use_version('minor')`
3031
* [ ] `devtools::submit_cran()`
3132
* [ ] Approve email
3233
3334
Wait for CRAN...
3435
3536
* [ ] Accepted :tada:
37+
* [ ] `usethis::github_unlock_branch()`
3638
* [ ] Finish & publish blog post
3739
* [ ] Add link to blog post in pkgdown news menu
3840
* [ ] `usethis::use_github_release()`
@@ -60,13 +62,15 @@
6062
6163
Submit to CRAN:
6264
65+
* [ ] `usethis::github_lock_branch()`
6366
* [ ] `usethis::use_version('patch')`
6467
* [ ] `devtools::submit_cran()`
6568
* [ ] Approve email
6669
6770
Wait for CRAN...
6871
6972
* [ ] Accepted :tada:
73+
* [ ] `usethis::github_unlock_branch()`
7074
* [ ] `usethis::use_github_release()`
7175
* [ ] `usethis::use_dev_version(push = TRUE)`
7276
* [ ] `usethis::use_news_md()`
@@ -93,13 +97,15 @@
9397
9498
Submit to CRAN:
9599
100+
* [ ] `usethis::github_lock_branch()`
96101
* [ ] `usethis::use_version('major')`
97102
* [ ] `devtools::submit_cran()`
98103
* [ ] Approve email
99104
100105
Wait for CRAN...
101106
102107
* [ ] Accepted :tada:
108+
* [ ] `usethis::github_unlock_branch()`
103109
* [ ] Finish & publish blog post
104110
* [ ] Add link to blog post in pkgdown news menu
105111
* [ ] `usethis::use_github_release()`
@@ -148,13 +154,15 @@
148154
149155
Submit to CRAN:
150156
157+
* [ ] `usethis::github_lock_branch()`
151158
* [ ] `usethis::use_version('major')`
152159
* [ ] `devtools::submit_cran()`
153160
* [ ] Approve email
154161
155162
Wait for CRAN...
156163
157164
* [ ] Accepted :tada:
165+
* [ ] `usethis::github_unlock_branch()`
158166
* [ ] Finish & publish blog post
159167
* [ ] Add link to blog post in pkgdown news menu
160168
* [ ] `usethis::use_github_release()`
@@ -184,13 +192,15 @@
184192
185193
Submit to CRAN:
186194
195+
* [ ] `usethis::github_lock_branch()`
187196
* [ ] `usethis::use_version('major')`
188197
* [ ] `devtools::submit_cran()`
189198
* [ ] Approve email
190199
191200
Wait for CRAN...
192201
193202
* [ ] Accepted :tada:
203+
* [ ] `usethis::github_unlock_branch()`
194204
* [ ] Finish & publish blog post
195205
* [ ] Add link to blog post in pkgdown news menu
196206
* [ ] `usethis::use_github_release()`

0 commit comments

Comments
 (0)