From ff9f5f5177529553d89a289e8682681289b46be9 Mon Sep 17 00:00:00 2001 From: pat-s Date: Wed, 15 Jul 2026 15:43:02 +0000 Subject: [PATCH] fix(local): shell-quote git args in the auto-patch push and trial-build gate (#126) ## Problem The first real `auto-apply-patches` run classified, limited to the top-10, and validated the candidate registry cleanly, then died at the push step: ``` Validating candidate registry: Patch registry OK (13 entries). sh: syntax error: unexpected "(" Error in git("commit", "-m", ...): git commit -m feat(patches): auto-propose 10 registry entries ... failed ``` Root cause: R's `system2()` with captured output (`stdout=TRUE`) runs the command through `/bin/sh`, and the arguments were passed **unquoted**. The commit message `feat(patches): ...` contains `()`, which the shell tried to interpret. The same class of bug affects the `^{commit}` and `ref:path` git refs in the trial-build gate. ## Fix `shQuote()` every git argument: - `propose-patches.R` -- the `git()` helper used by `--open-pr` (commit, push, checkout). - `trial-build-registry.R` -- the base-ref reads (`rev-parse ... ^{commit}`, `ls-tree`, `show ref:path`). ## Verification - Reproduced against a real git repo: the unquoted call fails with status 2 (the same `unexpected "("`); the `shQuote`d call commits successfully, and `rev-parse HEAD^{commit}` resolves. - 105 tests pass; all pre-commit hooks pass. Everything else in that run was correct: 842,659 failing builds classified, RcppParallel's 895 dependents correctly reported as blocked (not proposed), top-10 tbb-stddef candidates selected, 69 deferred, registry validated. Only the shell quoting was broken. Reviewed-on: https://git.devxy.io/devxy/build-cran-binaries/pulls/126 --- local/propose-patches.R | 4 +++- local/trial-build-registry.R | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/local/propose-patches.R b/local/propose-patches.R index 11085f0..7008251 100644 --- a/local/propose-patches.R +++ b/local/propose-patches.R @@ -435,7 +435,9 @@ if (do_write) { save_ledger(merge_ledger(load_ledger(), new_ledger_records)) git <- function(...) { - st <- system2("git", c(...), stdout = TRUE, stderr = TRUE) + # system2() with captured output runs via /bin/sh, so shell-quote every arg + # (commit messages contain "()", refs contain "^{}", etc.). + st <- system2("git", shQuote(c(...)), stdout = TRUE, stderr = TRUE) if (!identical(attr(st, "status"), NULL)) { stop(sprintf( "git %s failed:\n%s", diff --git a/local/trial-build-registry.R b/local/trial-build-registry.R index f649b62..0749bf7 100644 --- a/local/trial-build-registry.R +++ b/local/trial-build-registry.R @@ -52,9 +52,16 @@ current <- if (file.exists(registry_file)) { # silently treating the base as empty would trial-build the WHOLE registry # instead of just the entries the branch adds. registry_rel <- "local/patches/registry.json" +# system2() with captured output runs via /bin/sh, so shell-quote the git args +# (refs contain "^{}" and ":" that the shell would otherwise mangle). ref_ok <- suppressWarnings(system2( "git", - c("rev-parse", "--verify", "--quiet", sprintf("%s^{commit}", base_ref)), + shQuote(c( + "rev-parse", + "--verify", + "--quiet", + sprintf("%s^{commit}", base_ref) + )), stdout = TRUE, stderr = FALSE )) @@ -63,14 +70,14 @@ if (!is.null(attr(ref_ok, "status"))) { } in_base <- suppressWarnings(system2( "git", - c("ls-tree", base_ref, "--", registry_rel), + shQuote(c("ls-tree", base_ref, "--", registry_rel)), stdout = TRUE, stderr = FALSE )) file_in_base <- length(in_base) > 0L && any(nzchar(in_base)) base_json <- suppressWarnings(system2( "git", - c("show", sprintf("%s:%s", base_ref, registry_rel)), + shQuote(c("show", sprintf("%s:%s", base_ref, registry_rel))), stdout = TRUE, stderr = FALSE ))