# One-off maintenance: collapse the duplicate arch subsections that accumulated # in the "Missing package binaries for latest version ()" issues. # # A bug in weekly-missing-binaries-audit.R matched the existing "### " # subsection by its bare header while writing headers with a # " (N missing, M to rebuild)" suffix, so every audit run appended a fresh block # instead of replacing it. This script rewrites each "## " section to # keep only the *last* (freshest) block per arch. The audit fix prevents further # accumulation; this cleans up what is already there. # # Env: FORGEJO_TOKEN (required). DRY_RUN=1 to preview counts without patching. library(httr2, quietly = TRUE) forgejo_base <- "https://git.devxy.io/api/v1" repo <- "devxy/build-cran-binaries" token <- Sys.getenv("FORGEJO_TOKEN") dry_run <- nchar(Sys.getenv("DRY_RUN")) > 0 if (nchar(token) == 0) { stop("FORGEJO_TOKEN env var is not set") } issue_titles <- c( "Missing package binaries for latest version (Alpine)", "Missing package binaries for latest version (Ubuntu)", "Missing package binaries for latest version (Red Hat)" ) # Collapse one "## " block: keep only the last block per arch, # emitted in order of first appearance. `pl[1]` is the "## " header. dedupe_platform <- function(pl) { sub_hdr <- which(grepl("^### ", pl)) if (length(sub_hdr) == 0) { return(pl) } preamble <- pl[seq_len(sub_hdr[1] - 1)] sub_end <- c(sub_hdr[-1] - 1, length(pl)) blocks <- lapply(seq_along(sub_hdr), function(k) { pl[seq(sub_hdr[k], sub_end[k])] }) arches <- vapply( blocks, function(b) sub("^### (\\S+).*", "\\1", b[1]), character(1) ) # Index of the last block for each arch, kept in first-appearance order. last_idx <- vapply( unique(arches), function(a) max(which(arches == a)), integer(1) ) keep <- sort(last_idx) out <- preamble for (i in keep) { out <- c(out, blocks[[i]]) } out } process_issue <- function(title) { search_url <- sprintf( "%s/repos/%s/issues?type=issues&state=open&q=%s&limit=50", forgejo_base, repo, utils::URLencode(title, reserved = TRUE) ) resp <- request(search_url) |> req_headers(Authorization = paste("token", token)) |> req_perform() issues <- resp_body_json(resp, simplifyVector = FALSE) match_idx <- which(vapply(issues, function(x) x$title, character(1)) == title) if (length(match_idx) == 0) { cat(sprintf("[skip] No issue found: %s\n", title)) return(invisible()) } issue_number <- issues[[match_idx[1]]]$number body <- issues[[match_idx[1]]]$body if (is.null(body) || nchar(body) == 0) { cat(sprintf("[skip] Empty body: #%d %s\n", issue_number, title)) return(invisible()) } lines <- strsplit(body, "\n", fixed = TRUE)[[1]] before <- sum(grepl("^### ", lines)) # Split off the "## Excluded packages" footer so it is preserved verbatim. excl_idx <- which(lines == "## Excluded packages") footer <- character(0) if (length(excl_idx) > 0) { pre_dash <- which(lines == "---" & seq_along(lines) < excl_idx[1]) cut <- if (length(pre_dash) > 0) pre_dash[length(pre_dash)] else excl_idx[1] footer <- lines[seq(cut, length(lines))] lines <- lines[seq_len(cut - 1)] } # Platform headers ("## "); everything before the first is preamble. plat_idx <- which(grepl("^## ", lines)) if (length(plat_idx) == 0) { cat(sprintf("[skip] No platform sections: #%d %s\n", issue_number, title)) return(invisible()) } top <- lines[seq_len(plat_idx[1] - 1)] plat_end <- c(plat_idx[-1] - 1, length(lines)) new_lines <- top for (j in seq_along(plat_idx)) { pl <- lines[seq(plat_idx[j], plat_end[j])] new_lines <- c(new_lines, dedupe_platform(pl)) } if (length(footer) > 0) { new_lines <- c(new_lines, footer) } after <- sum(grepl("^### ", new_lines)) cat(sprintf( "#%d %s: %d -> %d arch subsections%s\n", issue_number, title, before, after, if (dry_run) " (dry run, not patched)" else "" )) if (dry_run) { return(invisible()) } patch_url <- sprintf( "%s/repos/%s/issues/%d", forgejo_base, repo, issue_number ) request(patch_url) |> req_headers( Authorization = paste("token", token), `Content-Type` = "application/json" ) |> req_body_json(list(body = paste(new_lines, collapse = "\n"))) |> req_method("PATCH") |> req_perform() cat(sprintf(" patched #%d\n", issue_number)) } for (t in issue_titles) { process_issue(t) } cat("Done.\n")