diff --git a/DESCRIPTION b/DESCRIPTION index dee48b3..808cbb0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: multiverse.internals Title: Internal Infrastructure for R-multiverse Description: R-multiverse requires this internal internal infrastructure package to automate contribution reviews and populate universes. -Version: 0.2.8 +Version: 0.2.9 License: MIT + file LICENSE URL: https://github.com/r-multiverse/multiverse.internals BugReports: https://github.com/r-multiverse/multiverse.internals/issues diff --git a/NAMESPACE b/NAMESPACE index c73085a..caa34ec 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,7 @@ export(record_versions) export(review_pull_request) export(review_pull_requests) export(try_message) -export(update_production) +export(update_staging) importFrom(gh,gh) importFrom(igraph,V) importFrom(igraph,graph) diff --git a/NEWS.md b/NEWS.md index eec36d3..0976711 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# multiverse.internals 0.2.9 + +* Implement community/staging idea from (@jeroen). + # multiverse.internals 0.2.8 * Only merge contributions created through the web interface. diff --git a/R/record_issues.R b/R/record_issues.R index 4c039be..b11a9ff 100644 --- a/R/record_issues.R +++ b/R/record_issues.R @@ -6,8 +6,7 @@ #' @section Package issues: #' Functions like [issues_versions()] and [issues_descriptions()] #' perform health checks for all packages in R-multiverse. -#' Only packages that pass these checks go to the production repository at -#' . For a complete list of checks, see +#' For a complete list of checks, see #' the `issues_*()` functions listed at #' . #' [record_versions()] updates the version number history diff --git a/R/update_production.R b/R/update_production.R deleted file mode 100644 index 8374bef..0000000 --- a/R/update_production.R +++ /dev/null @@ -1,172 +0,0 @@ -#' @title Update production -#' @export -#' @description Update the production universe. -#' @details [update_production()] controls how packages enter and leave -#' the production universe. It updates the production `packages.json` -#' manifest depending on the contents of the community -#' universe and issues with package checks. There are 3 phases: -#' 1. Demote packages: packages with any check issues in production -#' are given `days_notice` days to fix the problems. If the problems -#' are fixed on time, then the package stays in -#' production, and the notice period resets (the next problem is given -#' the full `days_notice` notice period from scratch). Otherwise, -#' if there are still issues after `days_notice` days, then the package -#' is removed from the `packages.json` manifest and added to a special -#' `removing.json` manifest in production. `removing.json` ensures -#' that the builds are actually removed from production before the -#' package can be promoted again. That way, if the next automatic -#' promotion fails production checks, no build will be available -#' to install from . -#' 2. Clear removals: a demoted package stays in `removing.json` until -#' the builds are removed from . -#' After the builds are gone, the package is removed from `removing.json` -#' so it can be promoted again. No build will become available again -#' until the package passes all production checks. -#' 3. Promote packages: a package in the community universe is moved -#' to production if: -#' * It passes description checks from [issues_descriptions()]. -#' * It passes version checks from [issues_versions()]. -#' * The package is available to install from -#' . -#' * The package is not in `removing.json`. -#' To promote the package, an entry is created in the production -#' `packages.json` with the Git hash of the latest release. -#' @return `NULL` (invisibly) -#' @inheritParams record_issues -#' @param path_production Character string, directory path to the source -#' files of the production universe. -#' @param path_community Character string, directory path to the source -#' files of the community universe. -#' @param repo_production Character string, URL of the production universe. -#' @param repo_community Character string, URL of the community universe. -#' @param days_notice Integer scalar, number of days between the -#' detection of a production issue and removal from the production universe. -#' @examples -#' \dontrun{ -#' url_production = "https://github.com/r-multiverse/production" -#' url_community = "https://github.com/r-multiverse/community" -#' path_production <- tempfile() -#' path_community <- tempfile() -#' gert::git_clone(url = url_production, path = path_production) -#' gert::git_clone(url = url_community, path = path_community) -#' update_production( -#' repo_production = "https://production.r-multiverse.org", -#' repo_community = "https://community.r-multiverse.org", -#' path_production = path_production, -#' path_community = path_community, -#' days_notice = 28L -#' ) -#' } -update_production <- function( - path_production, - path_community, - repo_production = "https://production.r-multiverse.org", - repo_community = "https://community.r-multiverse.org", - days_notice = 28L, - mock = NULL -) { - meta_production <- mock$production %||% meta_packages(repo_production) - meta_community <- mock$community %||% meta_packages(repo_community) - demote_packages( - path_production = path_production, - days_notice = days_notice - ) - clear_removed( - path_production = path_production, - meta_production = meta_production - ) - promote_packages( - path_production = path_production, - path_community = path_community, - meta_community = meta_community - ) - invisible() -} - -demote_packages <- function(path_production, days_notice) { - packages <- get_demote(path_production, days_notice) - removing <- sort(unique(c(get_removing(path_production), packages))) - file_removing <- file.path(path_production, "removing.json") - jsonlite::write_json(removing, file_removing, pretty = TRUE) - file_production <- file.path(path_production, "packages.json") - json <- jsonlite::read_json(file_production, simplifyVector = TRUE) - json <- json[!(json$package %in% removing),, drop = FALSE] # nolint - jsonlite::write_json(json, file_production, pretty = TRUE) -} - -clear_removed <- function(path_production, meta_production) { - removing <- intersect( - get_removing(path_production), - meta_production$package - ) - jsonlite::write_json( - removing, - file.path(path_production, "removing.json"), - pretty = TRUE - ) -} - -promote_packages <- function( - path_production, - path_community, - meta_community -) { - packages <- get_promote(path_production, path_community, meta_community) - file_production <- file.path(path_production, "packages.json") - file_community <- file.path(path_community, "packages.json") - json_production <- jsonlite::read_json( - file_production, - simplifyVector = TRUE - ) - json_community <- jsonlite::read_json(file_community, simplifyVector = TRUE) - promote <- json_community[json_community$package %in% packages,, drop = FALSE] # nolint - meta_community <- meta_community[, c("package", "remotesha")] - promote <- merge(promote, meta_community, all.x = TRUE, all.y = FALSE) - promote$branch <- promote$remotesha - promote$remotesha <- NULL - replace <- !(json_production$package %in% packages) - json_production <- json_production[replace,, drop = FALSE] # nolint - json_production <- rbind(json_production, promote) - json_production <- json_production[order(json_production$package),, drop = FALSE ] # nolint - jsonlite::write_json(json_production, file_production, pretty = TRUE) -} - -get_demote <- function(path_production, days_notice) { - file <- file.path(path_production, "packages.json") - packages <- jsonlite::read_json(file, simplifyVector = TRUE)$package - issues <- list.files(file.path(path_production, "issues")) - Filter( - x = intersect(packages, issues), - f = function(package) { - file <- file.path(path_production, "issues", package) - json <- jsonlite::read_json(path = file) - delay <- as.integer(Sys.Date() - as.Date(as.character(json$date))) - delay > days_notice - } - ) -} - -get_promote <- function(path_production, path_community, meta_community) { - issues <- Filter( - x = list.files(file.path(path_community, "issues")), - f = function(package) { - json <- jsonlite::read_json( - path = file.path(path_community, "issues", package) - ) - any(names(json) %in% c("descriptions", "versions")) - } - ) - removing <- get_removing(path_production) - file_community <- file.path(path_community, "packages.json") - json <- jsonlite::read_json(file_community, simplifyVector = TRUE) - candidates <- intersect(meta_community$package, json$package) - setdiff(candidates, c(issues, removing)) -} - -get_removing <- function(path_production) { - file <- file.path(path_production, "removing.json") - if (!file.exists(file)) { - return(character(0L)) - } - as.character(jsonlite::read_json(file, simplifyVector = TRUE)) -} diff --git a/R/update_staging.R b/R/update_staging.R new file mode 100644 index 0000000..4b57cc6 --- /dev/null +++ b/R/update_staging.R @@ -0,0 +1,73 @@ +#' @title Update staging +#' @export +#' @description Update the staging universe. +#' @details [update_staging()] controls how packages enter and leave +#' the staging universe. It updates the staging `packages.json` +#' manifest depending on the contents of the community +#' universe and issues with package checks. +#' @return `NULL` (invisibly) +#' @inheritParams record_issues +#' @param path_staging Character string, directory path to the source +#' files of the staging universe. +#' @param path_community Character string, directory path to the source +#' files of the community universe. +#' @param repo_community Character string, URL of the community universe. +#' @examples +#' \dontrun{ +#' url_staging = "https://github.com/r-multiverse/staging" +#' url_community = "https://github.com/r-multiverse/community" +#' path_staging <- tempfile() +#' path_community <- tempfile() +#' gert::git_clone(url = url_staging, path = path_staging) +#' gert::git_clone(url = url_community, path = path_community) +#' update_staging( +#' path_staging = path_staging, +#' path_community = path_community, +#' repo_community = "https://community.r-multiverse.org" +#' ) +#' } +update_staging <- function( + path_staging, + path_community, + repo_community = "https://community.r-multiverse.org", + mock = NULL +) { + meta_community <- mock$community %||% meta_packages(repo_community) + packages <- promotions(path_community, meta_community) + file_staging <- file.path(path_staging, "packages.json") + file_community <- file.path(path_community, "packages.json") + json_staging <- jsonlite::read_json(file_staging, simplifyVector = TRUE) + json_community <- jsonlite::read_json(file_community, simplifyVector = TRUE) + index_promote <- json_community$package %in% packages + promote <- json_community[index_promote,, drop = FALSE] # nolint + meta_community <- meta_community[, c("package", "remotesha")] + promote <- merge(promote, meta_community, all.x = TRUE, all.y = FALSE) + promote$branch <- promote$remotesha + promote$remotesha <- NULL + replace <- !(json_staging$package %in% packages) + json_staging <- json_staging[replace,, drop = FALSE] # nolint + json_staging <- rbind(json_staging, promote) + json_staging <- json_staging[order(json_staging$package),, drop = FALSE ] # nolint + jsonlite::write_json(json_staging, file_staging, pretty = TRUE) + invisible() +} + +promotions <- function(path_community, meta_community) { + promotion_checks <- c( + "descriptions", + "versions" + ) + issues <- Filter( + x = list.files(file.path(path_community, "issues")), + f = function(package) { + json <- jsonlite::read_json( + path = file.path(path_community, "issues", package) + ) + any(names(json) %in% promotion_checks) + } + ) + file_community <- file.path(path_community, "packages.json") + json <- jsonlite::read_json(file_community, simplifyVector = TRUE) + candidates <- intersect(meta_community$package, json$package) + setdiff(candidates, issues) +} diff --git a/README.md b/README.md index 12cbac5..76980ee 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,4 @@ `multiverse.internals` is an R package to support the internal infrastructure for the R-multiverse project. -To learn about specific pieces of the R-multiverse infrastructure for reviewing and checking packages, please visit . +To learn about specific pieces of the R-multiverse infrastructure for reviewing and checking packages, please visit . diff --git a/_pkgdown.yml b/_pkgdown.yml index a2aa788..f0eee27 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -23,6 +23,6 @@ reference: contents: - record_issues - record_versions -- title: Production +- title: Staging contents: - - update_production + - update_staging diff --git a/inst/mock/community/issues/production-removing b/inst/mock/community/issues/checks similarity index 77% rename from inst/mock/community/issues/production-removing rename to inst/mock/community/issues/checks index 182e1a1..f07a79c 100644 --- a/inst/mock/community/issues/production-removing +++ b/inst/mock/community/issues/checks @@ -5,9 +5,9 @@ "_wasmbinary": ["none"], "_winbinary": ["success"], "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624562787"] + "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/12345"] }, "date": ["1980-01-01"], "version": ["2.0.2"], - "remote_hash": ["476886da2b41a4d937543278dc12044889c9d4ca"] + "remote_hash": ["abcdef1234567890abcdef"] } diff --git a/inst/mock/community/issues/community-descriptions b/inst/mock/community/issues/community-descriptions deleted file mode 100644 index 38d568e..0000000 --- a/inst/mock/community/issues/community-descriptions +++ /dev/null @@ -1,16 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["failure"], - "_macbinary": ["success"], - "_wasmbinary": ["success"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624565626"] - }, - "descriptions": { - "remotes": ["hyunjimoon/SBC", "stan-dev/cmdstanr"] - }, - "date": ["2024-06-21"], - "version": ["0.1.1"], - "remote_hash": ["bbdda1b4a44a3d6a22041e03eed38f27319d8f32"] -} diff --git a/inst/mock/community/issues/community-notice b/inst/mock/community/issues/community-notice deleted file mode 100644 index bdb77ba..0000000 --- a/inst/mock/community/issues/community-notice +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624561349"] - }, - "date": ["9999-12-31"], - "version": ["2.0.2"], - "remote_hash": ["b0a5ec38638ca69e3adbc7b24d815757e5f74817"] -} diff --git a/inst/mock/community/issues/community-remove b/inst/mock/community/issues/community-remove deleted file mode 100644 index 182e1a1..0000000 --- a/inst/mock/community/issues/community-remove +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624562787"] - }, - "date": ["1980-01-01"], - "version": ["2.0.2"], - "remote_hash": ["476886da2b41a4d937543278dc12044889c9d4ca"] -} diff --git a/inst/mock/community/issues/descriptions b/inst/mock/community/issues/descriptions new file mode 100644 index 0000000..5368252 --- /dev/null +++ b/inst/mock/community/issues/descriptions @@ -0,0 +1,8 @@ +{ + "descriptions": { + "remotes": ["owner1/repo1", "owner2/repo2"] + }, + "date": ["2024-06-21"], + "version": ["0.1.1"], + "remote_hash": ["abcdefabcdef1234567890"] +} diff --git a/inst/mock/community/issues/production-notice b/inst/mock/community/issues/production-notice deleted file mode 100644 index bdb77ba..0000000 --- a/inst/mock/community/issues/production-notice +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624561349"] - }, - "date": ["9999-12-31"], - "version": ["2.0.2"], - "remote_hash": ["b0a5ec38638ca69e3adbc7b24d815757e5f74817"] -} diff --git a/inst/mock/community/issues/community-versions b/inst/mock/community/issues/versions similarity index 72% rename from inst/mock/community/issues/community-versions rename to inst/mock/community/issues/versions index 909f8a6..f442864 100644 --- a/inst/mock/community/issues/community-versions +++ b/inst/mock/community/issues/versions @@ -6,6 +6,6 @@ "hash_highest": ["hash_1.0.0"] }, "date": ["2024-01-01"], - "version": [], - "remote_hash": [] + "version": ["1.0.0"], + "remote_hash": ["abcdefabcdefabcdef123123123"] } diff --git a/inst/mock/community/packages.json b/inst/mock/community/packages.json index 6e3cf3f..e054a4d 100644 --- a/inst/mock/community/packages.json +++ b/inst/mock/community/packages.json @@ -1,47 +1,32 @@ [ { - "package": "community-good", - "url": "https://github.com/owner/community-good", + "package": "promote", + "url": "https://github.com/owner/promote", "branch": "*release" }, { - "package": "community-descriptions", - "url": "https://github.com/owner/community-descriptions", + "package": "change", + "url": "https://github.com/owner/change", "branch": "*release" }, { - "package": "community-notice", - "url": "https://github.com/owner/community-notice", + "package": "keep", + "url": "https://github.com/owner/keep", "branch": "*release" }, { - "package": "community-remove", - "url": "https://github.com/owner/community-remove", + "package": "checks", + "url": "https://github.com/owner/checks", "branch": "*release" }, { - "package": "community-versions", - "url": "https://github.com/owner/community-versions", + "package": "descriptions", + "url": "https://github.com/owner/descriptions", "branch": "*release" }, { - "package": "production-good", - "url": "https://github.com/owner/production-good", - "branch": "*release" - }, - { - "package": "production-notice", - "url": "https://github.com/owner/production-notice", - "branch": "*release" - }, - { - "package": "production-remove", - "url": "https://github.com/owner/production-remove", - "branch": "*release" - }, - { - "package": "production-removing", - "url": "https://github.com/owner/production-removing", + "package": "versions", + "url": "https://github.com/owner/versions", "branch": "*release" } ] diff --git a/inst/mock/production/issues/community-descriptions b/inst/mock/production/issues/community-descriptions deleted file mode 100644 index 38d568e..0000000 --- a/inst/mock/production/issues/community-descriptions +++ /dev/null @@ -1,16 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["failure"], - "_macbinary": ["success"], - "_wasmbinary": ["success"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624565626"] - }, - "descriptions": { - "remotes": ["hyunjimoon/SBC", "stan-dev/cmdstanr"] - }, - "date": ["2024-06-21"], - "version": ["0.1.1"], - "remote_hash": ["bbdda1b4a44a3d6a22041e03eed38f27319d8f32"] -} diff --git a/inst/mock/production/issues/community-notice b/inst/mock/production/issues/community-notice deleted file mode 100644 index bdb77ba..0000000 --- a/inst/mock/production/issues/community-notice +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624561349"] - }, - "date": ["9999-12-31"], - "version": ["2.0.2"], - "remote_hash": ["b0a5ec38638ca69e3adbc7b24d815757e5f74817"] -} diff --git a/inst/mock/production/issues/community-remove b/inst/mock/production/issues/community-remove deleted file mode 100644 index 182e1a1..0000000 --- a/inst/mock/production/issues/community-remove +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624562787"] - }, - "date": ["1980-01-01"], - "version": ["2.0.2"], - "remote_hash": ["476886da2b41a4d937543278dc12044889c9d4ca"] -} diff --git a/inst/mock/production/issues/community-versions b/inst/mock/production/issues/community-versions deleted file mode 100644 index 909f8a6..0000000 --- a/inst/mock/production/issues/community-versions +++ /dev/null @@ -1,11 +0,0 @@ -{ - "versions": { - "version_current": ["0.0.1"], - "hash_current": ["hash_0.0.1"], - "version_highest": ["1.0.0"], - "hash_highest": ["hash_1.0.0"] - }, - "date": ["2024-01-01"], - "version": [], - "remote_hash": [] -} diff --git a/inst/mock/production/issues/production-notice b/inst/mock/production/issues/production-notice deleted file mode 100644 index bdb77ba..0000000 --- a/inst/mock/production/issues/production-notice +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624561349"] - }, - "date": ["9999-12-31"], - "version": ["2.0.2"], - "remote_hash": ["b0a5ec38638ca69e3adbc7b24d815757e5f74817"] -} diff --git a/inst/mock/production/issues/production-remove b/inst/mock/production/issues/production-remove deleted file mode 100644 index 182e1a1..0000000 --- a/inst/mock/production/issues/production-remove +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624562787"] - }, - "date": ["1980-01-01"], - "version": ["2.0.2"], - "remote_hash": ["476886da2b41a4d937543278dc12044889c9d4ca"] -} diff --git a/inst/mock/production/issues/production-removing b/inst/mock/production/issues/production-removing deleted file mode 100644 index 182e1a1..0000000 --- a/inst/mock/production/issues/production-removing +++ /dev/null @@ -1,13 +0,0 @@ -{ - "checks": { - "_linuxdevel": ["success"], - "_macbinary": ["success"], - "_wasmbinary": ["none"], - "_winbinary": ["success"], - "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624562787"] - }, - "date": ["1980-01-01"], - "version": ["2.0.2"], - "remote_hash": ["476886da2b41a4d937543278dc12044889c9d4ca"] -} diff --git a/inst/mock/production/packages.json b/inst/mock/production/packages.json deleted file mode 100644 index 0de6073..0000000 --- a/inst/mock/production/packages.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "package": "production-good", - "url": "https://github.com/owner/production-good", - "branch": "sha-production-good" - }, - { - "package": "production-notice", - "url": "https://github.com/owner/production-notice", - "branch": "sha-production-notice" - }, - { - "package": "production-remove", - "url": "https://github.com/owner/production-remove", - "branch": "sha-production-remove" - } -] diff --git a/inst/mock/production/removing.json b/inst/mock/production/removing.json deleted file mode 100644 index 7481bb9..0000000 --- a/inst/mock/production/removing.json +++ /dev/null @@ -1 +0,0 @@ -["production-removed", "production-removing"] diff --git a/inst/mock/community/issues/production-remove b/inst/mock/staging/issues/checks similarity index 77% rename from inst/mock/community/issues/production-remove rename to inst/mock/staging/issues/checks index 182e1a1..f07a79c 100644 --- a/inst/mock/community/issues/production-remove +++ b/inst/mock/staging/issues/checks @@ -5,9 +5,9 @@ "_wasmbinary": ["none"], "_winbinary": ["success"], "_status": ["success"], - "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/9624562787"] + "_buildurl": ["https://github.com/r-universe/r-multiverse/actions/runs/12345"] }, "date": ["1980-01-01"], "version": ["2.0.2"], - "remote_hash": ["476886da2b41a4d937543278dc12044889c9d4ca"] + "remote_hash": ["abcdef1234567890abcdef"] } diff --git a/inst/mock/staging/packages.json b/inst/mock/staging/packages.json new file mode 100644 index 0000000..cf41733 --- /dev/null +++ b/inst/mock/staging/packages.json @@ -0,0 +1,12 @@ +[ + { + "package": "change", + "url": "https://github.com/owner/change", + "branch": "original" + }, + { + "package": "keep", + "url": "https://github.com/owner/keep", + "branch": "sha-keep" + } +] diff --git a/man/issues_checks.Rd b/man/issues_checks.Rd index c13372e..25040be 100644 --- a/man/issues_checks.Rd +++ b/man/issues_checks.Rd @@ -29,8 +29,7 @@ happened during building and testing. Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} perform health checks for all packages in R-multiverse. -Only packages that pass these checks go to the production repository at -\url{https://production.r-multiverse.org}. For a complete list of checks, see +For a complete list of checks, see the \verb{issues_*()} functions listed at \url{https://r-multiverse.org/multiverse.internals/reference/index.html}. \code{\link[=record_versions]{record_versions()}} updates the version number history diff --git a/man/issues_dependencies.Rd b/man/issues_dependencies.Rd index 560c02b..c54616a 100644 --- a/man/issues_dependencies.Rd +++ b/man/issues_dependencies.Rd @@ -46,8 +46,7 @@ the explicit mentions in the \code{DESCRIPTION} file. Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} perform health checks for all packages in R-multiverse. -Only packages that pass these checks go to the production repository at -\url{https://production.r-multiverse.org}. For a complete list of checks, see +For a complete list of checks, see the \verb{issues_*()} functions listed at \url{https://r-multiverse.org/multiverse.internals/reference/index.html}. \code{\link[=record_versions]{record_versions()}} updates the version number history diff --git a/man/issues_descriptions.Rd b/man/issues_descriptions.Rd index 2046762..7233edd 100644 --- a/man/issues_descriptions.Rd +++ b/man/issues_descriptions.Rd @@ -29,8 +29,7 @@ package's description file, such as the presence of a Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} perform health checks for all packages in R-multiverse. -Only packages that pass these checks go to the production repository at -\url{https://production.r-multiverse.org}. For a complete list of checks, see +For a complete list of checks, see the \verb{issues_*()} functions listed at \url{https://r-multiverse.org/multiverse.internals/reference/index.html}. \code{\link[=record_versions]{record_versions()}} updates the version number history diff --git a/man/issues_versions.Rd b/man/issues_versions.Rd index d82ebd5..50e160d 100644 --- a/man/issues_versions.Rd +++ b/man/issues_versions.Rd @@ -31,8 +31,7 @@ greater than all the versions of all the previous package releases. Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} perform health checks for all packages in R-multiverse. -Only packages that pass these checks go to the production repository at -\url{https://production.r-multiverse.org}. For a complete list of checks, see +For a complete list of checks, see the \verb{issues_*()} functions listed at \url{https://r-multiverse.org/multiverse.internals/reference/index.html}. \code{\link[=record_versions]{record_versions()}} updates the version number history diff --git a/man/record_issues.Rd b/man/record_issues.Rd index 667043f..6a8158e 100644 --- a/man/record_issues.Rd +++ b/man/record_issues.Rd @@ -40,8 +40,7 @@ package-specific JSON files. Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} perform health checks for all packages in R-multiverse. -Only packages that pass these checks go to the production repository at -\url{https://production.r-multiverse.org}. For a complete list of checks, see +For a complete list of checks, see the \verb{issues_*()} functions listed at \url{https://r-multiverse.org/multiverse.internals/reference/index.html}. \code{\link[=record_versions]{record_versions()}} updates the version number history diff --git a/man/record_versions.Rd b/man/record_versions.Rd index 801e100..58450f0 100644 --- a/man/record_versions.Rd +++ b/man/record_versions.Rd @@ -40,8 +40,7 @@ practices for version numbers. Functions like \code{\link[=issues_versions]{issues_versions()}} and \code{\link[=issues_descriptions]{issues_descriptions()}} perform health checks for all packages in R-multiverse. -Only packages that pass these checks go to the production repository at -\url{https://production.r-multiverse.org}. For a complete list of checks, see +For a complete list of checks, see the \verb{issues_*()} functions listed at \url{https://r-multiverse.org/multiverse.internals/reference/index.html}. \code{\link[=record_versions]{record_versions()}} updates the version number history diff --git a/man/update_production.Rd b/man/update_production.Rd deleted file mode 100644 index ce41c28..0000000 --- a/man/update_production.Rd +++ /dev/null @@ -1,91 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/update_production.R -\name{update_production} -\alias{update_production} -\title{Update production} -\usage{ -update_production( - path_production, - path_community, - repo_production = "https://production.r-multiverse.org", - repo_community = "https://community.r-multiverse.org", - days_notice = 28L, - mock = NULL -) -} -\arguments{ -\item{path_production}{Character string, directory path to the source -files of the production universe.} - -\item{path_community}{Character string, directory path to the source -files of the community universe.} - -\item{repo_production}{Character string, URL of the production universe.} - -\item{repo_community}{Character string, URL of the community universe.} - -\item{days_notice}{Integer scalar, number of days between the -detection of a production issue and removal from the production universe.} - -\item{mock}{For testing purposes only, a named list of data frames -for inputs to various intermediate functions.} -} -\value{ -\code{NULL} (invisibly) -} -\description{ -Update the production universe. -} -\details{ -\code{\link[=update_production]{update_production()}} controls how packages enter and leave -the production universe. It updates the production \code{packages.json} -manifest depending on the contents of the community -universe and issues with package checks. There are 3 phases: -\enumerate{ -\item Demote packages: packages with any check issues in production -are given \code{days_notice} days to fix the problems. If the problems -are fixed on time, then the package stays in -production, and the notice period resets (the next problem is given -the full \code{days_notice} notice period from scratch). Otherwise, -if there are still issues after \code{days_notice} days, then the package -is removed from the \code{packages.json} manifest and added to a special -\code{removing.json} manifest in production. \code{removing.json} ensures -that the builds are actually removed from production before the -package can be promoted again. That way, if the next automatic -promotion fails production checks, no build will be available -to install from \url{https://production.r-multiverse.org}. -\item Clear removals: a demoted package stays in \code{removing.json} until -the builds are removed from \url{https://production.r-multiverse.org}. -After the builds are gone, the package is removed from \code{removing.json} -so it can be promoted again. No build will become available again -until the package passes all production checks. -\item Promote packages: a package in the community universe is moved -to production if: -\itemize{ -\item It passes description checks from \code{\link[=issues_descriptions]{issues_descriptions()}}. -\item It passes version checks from \code{\link[=issues_versions]{issues_versions()}}. -\item The package is available to install from -\url{https://community.r-multiverse.org}. -\item The package is not in \code{removing.json}. -To promote the package, an entry is created in the production -\code{packages.json} with the Git hash of the latest release. -} -} -} -\examples{ -\dontrun{ -url_production = "https://github.com/r-multiverse/production" -url_community = "https://github.com/r-multiverse/community" -path_production <- tempfile() -path_community <- tempfile() -gert::git_clone(url = url_production, path = path_production) -gert::git_clone(url = url_community, path = path_community) -update_production( - repo_production = "https://production.r-multiverse.org", - repo_community = "https://community.r-multiverse.org", - path_production = path_production, - path_community = path_community, - days_notice = 28L -) -} -} diff --git a/man/update_staging.Rd b/man/update_staging.Rd new file mode 100644 index 0000000..98a14ce --- /dev/null +++ b/man/update_staging.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/update_staging.R +\name{update_staging} +\alias{update_staging} +\title{Update staging} +\usage{ +update_staging( + path_staging, + path_community, + repo_community = "https://community.r-multiverse.org", + mock = NULL +) +} +\arguments{ +\item{path_staging}{Character string, directory path to the source +files of the staging universe.} + +\item{path_community}{Character string, directory path to the source +files of the community universe.} + +\item{repo_community}{Character string, URL of the community universe.} + +\item{mock}{For testing purposes only, a named list of data frames +for inputs to various intermediate functions.} +} +\value{ +\code{NULL} (invisibly) +} +\description{ +Update the staging universe. +} +\details{ +\code{\link[=update_staging]{update_staging()}} controls how packages enter and leave +the staging universe. It updates the staging \code{packages.json} +manifest depending on the contents of the community +universe and issues with package checks. +} +\examples{ +\dontrun{ +url_staging = "https://github.com/r-multiverse/staging" +url_community = "https://github.com/r-multiverse/community" +path_staging <- tempfile() +path_community <- tempfile() +gert::git_clone(url = url_staging, path = path_staging) +gert::git_clone(url = url_community, path = path_community) +update_staging( + path_staging = path_staging, + path_community = path_community, + repo_community = "https://community.r-multiverse.org" +) +} +} diff --git a/tests/testthat/test-update_production.R b/tests/testthat/test-update_production.R deleted file mode 100644 index ee6aef3..0000000 --- a/tests/testthat/test-update_production.R +++ /dev/null @@ -1,317 +0,0 @@ -test_that("promote_packages()", { - dir_production <- tempfile() - dir_community <- tempfile() - path_production <- file.path(dir_production, "production") - path_community <- file.path(dir_community, "community") - dir.create(dir_production) - dir.create(dir_community) - on.exit(unlink(path_production, recursive = TRUE)) - on.exit(unlink(path_community, recursive = TRUE), add = TRUE) - mock <- system.file( - "mock", - package = "multiverse.internals", - mustWork = TRUE - ) - file.copy( - from = file.path(mock, "production"), - to = dir_production, - recursive = TRUE - ) - file.copy( - from = file.path(mock, "community"), - to = dir_community, - recursive = TRUE - ) - json <- jsonlite::read_json(file.path(path_community, "packages.json")) - names <- vapply( - json, - function(x) x$package, - FUN.VALUE = character(1L) - ) - meta_community <- data.frame( - package = names, - remotesha = paste0("sha-", names) - ) - promote_packages( - path_production = path_production, - path_community = path_community, - meta = meta_community - ) - packages <- jsonlite::read_json( - file.path(path_production, "packages.json"), - simplifyVector = TRUE - ) - expect_true(is.data.frame(packages)) - expect_equal(dim(packages), c(6L, 3L)) - expect_equal( - sort(packages$package), - sort( - c( - "community-good", "community-notice", "community-remove", - "production-good", "production-notice", "production-remove" - ) - ) - ) - expect_equal( - packages$url, - file.path("https://github.com/owner", packages$package) - ) - expect_equal(packages$branch, paste0("sha-", packages$package)) - removing <- jsonlite::read_json( - file.path(path_production, "removing.json"), - simplifyVector = TRUE - ) - expect_equal( - sort(removing), - sort(c("production-removed", "production-removing")) - ) -}) - -test_that("clear_removed()", { - dir_production <- tempfile() - path_production <- file.path(dir_production, "production") - dir.create(dir_production) - on.exit(unlink(path_production, recursive = TRUE)) - mock <- system.file( - "mock", - package = "multiverse.internals", - mustWork = TRUE - ) - file.copy( - from = file.path(mock, "production"), - to = dir_production, - recursive = TRUE - ) - json <- jsonlite::read_json(file.path(path_production, "packages.json")) - names <- vapply( - json, - function(x) x$package, - FUN.VALUE = character(1L) - ) - names <- c(names, "production-removing") - meta_production <- data.frame( - package = names, - remotesha = paste0("sha-", names) - ) - clear_removed( - path_production = path_production, - meta_production = meta_production - ) - removing <- jsonlite::read_json( - file.path(path_production, "removing.json"), - simplifyVector = TRUE - ) - expect_equal(removing, "production-removing") -}) - -test_that("demote_packages()", { - dir_production <- tempfile() - path_production <- file.path(dir_production, "production") - dir.create(dir_production) - on.exit(unlink(path_production, recursive = TRUE)) - mock <- system.file( - "mock", - package = "multiverse.internals", - mustWork = TRUE - ) - file.copy( - from = file.path(mock, "production"), - to = dir_production, - recursive = TRUE - ) - demote_packages( - path_production = path_production, - days_notice = 28L - ) - packages <- jsonlite::read_json( - file.path(path_production, "packages.json"), - simplifyVector = TRUE - ) - expect_equal(dim(packages), c(2L, 3L)) - expect_equal( - sort(packages$package), - sort(c("production-good", "production-notice")) - ) - expect_equal( - packages$url, - file.path("https://github.com/owner", packages$package) - ) - expect_equal(packages$branch, paste0("sha-", packages$package)) - removing <- jsonlite::read_json( - file.path(path_production, "removing.json"), - simplifyVector = TRUE - ) - expect_equal( - sort(removing), - sort(c("production-remove", "production-removed", "production-removing")) - ) -}) - -test_that("update_production()", { - dir_production <- tempfile() - dir_community <- tempfile() - path_production <- file.path(dir_production, "production") - path_community <- file.path(dir_community, "community") - dir.create(dir_production) - dir.create(dir_community) - on.exit(unlink(path_production, recursive = TRUE)) - on.exit(unlink(path_community, recursive = TRUE), add = TRUE) - mock <- system.file( - "mock", - package = "multiverse.internals", - mustWork = TRUE - ) - file.copy( - from = file.path(mock, "production"), - to = dir_production, - recursive = TRUE - ) - file.copy( - from = file.path(mock, "community"), - to = dir_community, - recursive = TRUE - ) - names_production <- vapply( - jsonlite::read_json(file.path(path_production, "packages.json")), - function(x) x$package, - FUN.VALUE = character(1L) - ) - names_production <- c(names_production, "production-removing") - meta_production <- data.frame( - package = names_production, - remotesha = paste0("sha-", names_production) - ) - names_community <- vapply( - jsonlite::read_json(file.path(path_community, "packages.json")), - function(x) x$package, - FUN.VALUE = character(1L) - ) - meta_community <- data.frame( - package = names_community, - remotesha = paste0("sha-", names_community) - ) - # Update production source. Updating twice because this case should be - # idempotent. - for (index in seq_len(2L)) { - update_production( - path_production, - path_community, - days_notice = 28L, - mock = list(production = meta_production, community = meta_community) - ) - packages <- jsonlite::read_json( - file.path(path_production, "packages.json"), - simplifyVector = TRUE - ) - expect_equal(dim(packages), c(5L, 3L)) - expect_equal( - sort(packages$package), - sort( - c( - "community-good", "community-notice", "community-remove", - "production-good", "production-notice" - ) - ) - ) - expect_equal( - packages$url, - file.path("https://github.com/owner", packages$package) - ) - expect_equal(packages$branch, paste0("sha-", packages$package)) - removing <- jsonlite::read_json( - file.path(path_production, "removing.json"), - simplifyVector = TRUE - ) - expect_equal( - sort(removing), - sort(c("production-remove", "production-removing")) - ) - } - # Then the production universe catches up. - meta_production <- packages - meta_production$remotesha <- meta_production$branch - meta_production$remotesha <- NULL - meta_production$url <- NULL - # community-remove is now in production and needs to be removed. - # production-remove and production-removing are were automatically - # removed before, so they are free to get promoted again below. - # Update the production source again below. This should be idempotent - # because the issue files and the universe have not had a chance to catch up - # between two updates in quick succession. - for (index in seq_along(2L)) { - update_production( - path_production, - path_community, - days_notice = 28L, - mock = list(production = meta_production, community = meta_community) - ) - packages <- jsonlite::read_json( - file.path(path_production, "packages.json"), - simplifyVector = TRUE - ) - expect_equal(dim(packages), c(6L, 3L)) - expect_equal( - sort(packages$package), - sort( - c( - "community-good", "community-notice", - "production-good", "production-notice", - "production-remove", "production-removing" - ) - ) - ) - expect_equal( - packages$url, - file.path("https://github.com/owner", packages$package) - ) - expect_equal(packages$branch, paste0("sha-", packages$package)) - removing <- jsonlite::read_json( - file.path(path_production, "removing.json"), - simplifyVector = TRUE - ) - expect_equal(removing, "community-remove") - } - # Then the production universe catches up, adding - # production-remove and production-removing back to production, - # but removing community-remove. - meta_production <- packages - meta_production$remotesha <- meta_production$branch - meta_production$remotesha <- NULL - meta_production$url <- NULL - # On the next update, production-remove and production-removing - # should be demoted and marked for removal. community-remove, on the - # other hand should be promoted back. - update_production( - path_production, - path_community, - days_notice = 28L, - mock = list(production = meta_production, community = meta_community) - ) - packages <- jsonlite::read_json( - file.path(path_production, "packages.json"), - simplifyVector = TRUE - ) - expect_equal(dim(packages), c(5L, 3L)) - expect_equal( - sort(packages$package), - sort( - c( - "community-good", "community-notice", "community-remove", - "production-good", "production-notice" - ) - ) - ) - expect_equal( - packages$url, - file.path("https://github.com/owner", packages$package) - ) - expect_equal(packages$branch, paste0("sha-", packages$package)) - removing <- jsonlite::read_json( - file.path(path_production, "removing.json"), - simplifyVector = TRUE - ) - expect_equal( - sort(removing), - sort(c("production-remove", "production-removing")) - ) -}) diff --git a/tests/testthat/test-update_staging.R b/tests/testthat/test-update_staging.R new file mode 100644 index 0000000..0d6be7e --- /dev/null +++ b/tests/testthat/test-update_staging.R @@ -0,0 +1,53 @@ +test_that("update_staging()", { + dir_staging <- tempfile() + dir_community <- tempfile() + path_staging <- file.path(dir_staging, "staging") + path_community <- file.path(dir_community, "community") + dir.create(dir_staging) + dir.create(dir_community) + on.exit(unlink(path_staging, recursive = TRUE)) + on.exit(unlink(path_community, recursive = TRUE), add = TRUE) + mock <- system.file( + "mock", + package = "multiverse.internals", + mustWork = TRUE + ) + file.copy( + from = file.path(mock, "staging"), + to = dir_staging, + recursive = TRUE + ) + file.copy( + from = file.path(mock, "community"), + to = dir_community, + recursive = TRUE + ) + json <- jsonlite::read_json(file.path(path_community, "packages.json")) + names <- vapply( + json, + function(x) x$package, + FUN.VALUE = character(1L) + ) + meta_community <- data.frame( + package = names, + remotesha = paste0("sha-", names) + ) + update_staging( + path_staging = path_staging, + path_community = path_community, + mock = list(community = meta_community) + ) + packages <- jsonlite::read_json( + file.path(path_staging, "packages.json"), + simplifyVector = TRUE + ) + expect_true(is.data.frame(packages)) + expect_equal(dim(packages), c(4L, 3L)) + names <- c("change", "checks", "keep", "promote") + expect_equal(sort(packages$package), sort(names)) + expect_equal( + packages$url, + file.path("https://github.com/owner", packages$package) + ) + expect_equal(packages$branch, paste0("sha-", packages$package)) +})