From 24b58896c82f8f0ee189650e6c0fa23104be9716 Mon Sep 17 00:00:00 2001 From: vasilito Date: Sun, 12 Jul 2026 15:22:28 +0300 Subject: [PATCH] phase 16.1: sync-versions.sh safe-by-default + --dry-run + fix --regen-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - REGEN_LOCKFILES defaults to 0 (opt-in, not opt-out) - New --regen flag explicitly enables lockfile regeneration - New --dry-run flag: snapshots each Cargo.lock, regens, diffs, reverts Reports per-lockfile changes that WOULD occur without modifying files - Fixed --regen-only deadlock: CHECK_ONLY=1 no longer blocks regen (mode_regen_only gate added to CHECK_ONLY skip check) - Fixed ROOT_DRIFT unbound variable in --regen-only mode (: ${ROOT_DRIFT:=0} before summary section) - Moved regen_lockfile_for() definition before dry-run block (was defined after call site — caused 'command not found') --- local/scripts/sync-versions.sh | 97 ++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/local/scripts/sync-versions.sh b/local/scripts/sync-versions.sh index 11a8f4e592..c2178098dc 100755 --- a/local/scripts/sync-versions.sh +++ b/local/scripts/sync-versions.sh @@ -31,10 +31,12 @@ # - Meson test-case crates # # Usage: -# ./local/scripts/sync-versions.sh # Apply -# ./local/scripts/sync-versions.sh --check # Check only -# ./local/scripts/sync-versions.sh --no-regen # Apply without Cargo.lock regen -# ./local/scripts/sync-versions.sh --regen-only # Only regenerate Cargo.lock files +# ./local/scripts/sync-versions.sh # Apply version sync (SAFE — no lockfile regen by default) +# ./local/scripts/sync-versions.sh --regen # Apply sync + regen Cargo.lock files +# ./local/scripts/sync-versions.sh --check # Check only, exit 1 if drift +# ./local/scripts/sync-versions.sh --dry-run # Preview: show what --regen WOULD change in lockfiles +# ./local/scripts/sync-versions.sh --no-regen # Same as default (for backward compat) +# ./local/scripts/sync-versions.sh --regen-only # Only regenerate Cargo.lock files set -euo pipefail @@ -42,13 +44,16 @@ ROOT="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOT" CHECK_ONLY=0 -REGEN_LOCKFILES=1 +REGEN_LOCKFILES=0 # Phase 16.0: safe-by-default — lockfile regen is opt-in +DRY_RUN=0 for arg in "$@"; do case "$arg" in --check) CHECK_ONLY=1 ;; + --regen) REGEN_LOCKFILES=1 ;; --no-regen) REGEN_LOCKFILES=0 ;; --regen-only) REGEN_LOCKFILES=1; mode_regen_only=1 ;; - -h|--help) sed -n '1,40p' "$0"; exit 0 ;; + --dry-run) DRY_RUN=1; REGEN_LOCKFILES=1 ;; # dry-run implies --regen + -h|--help) sed -n '1,45p' "$0"; exit 0 ;; *) echo "Unknown option: $arg" >&2; exit 1 ;; esac done @@ -247,6 +252,8 @@ echo "" # ---- Summary ---- +: ${ROOT_DRIFT:=0} + if [[ $CHECK_ONLY -eq 1 ]]; then echo "Cat 0 (cookbook root): drift $ROOT_DRIFT" echo "Cat 1: checked $CAT1_CHECKED, drift $CAT1_DRIFT" @@ -273,21 +280,21 @@ fi # that has a Cargo.lock. The path-dep + [patch.crates-io] config in each # fork ensures the right crates are pulled in. # -# Skip when --check or --no-regen; only --regen-only also opts in. +# Skip when --check (but NOT --regen-only: that opts IN to regen) +# Skip when --no-regen is active (REGEN_LOCKFILES=0) -if [[ $CHECK_ONLY -eq 1 ]]; then +if [[ $CHECK_ONLY -eq 1 && "${mode_regen_only:-0}" != "1" ]]; then echo "" echo "(lockfile regeneration skipped in --check mode)" exit 0 fi if [[ $REGEN_LOCKFILES -eq 0 ]]; then echo "" - echo "(lockfile regeneration skipped via --no-regen)" + echo "(lockfile regeneration skipped — use --regen to enable)" exit 0 fi -echo "" -echo "=== Phase 3: Regenerate Cargo.lock files ===" +# ---- Phase 3: Lockfile regeneration utilities ---- REGEN_DONE=0 REGEN_FAILED=0 @@ -314,6 +321,74 @@ regen_lockfile_for() { cd "$ROOT" } +# ---- Phase 3: Dry-run preview (--dry-run flag) ---- +# Snapshot each fork's Cargo.lock, regen, show diff, then revert. +# This lets operators preview lockfile changes before a real branch bump. +if [[ $DRY_RUN -eq 1 ]]; then + echo "" + echo "=== Phase 3: Dry-run lockfile regen preview ===" + echo "(existing Cargo.lock files will NOT be modified)" + echo "" + + # Preserve all lockfiles first + declare -A LOCK_BACKUPS=() + for lock_dir in "$ROOT" local/sources/*/; do + [ -d "$lock_dir" ] || continue + [ -f "${lock_dir}/Cargo.toml" ] || continue + lock="${lock_dir}/Cargo.lock" + if [ -f "$lock" ]; then + LOCK_BACKUPS["$lock"]=$(cat "$lock") + fi + done + + # Regenerate in-place (reverts after) + regen_lockfile_for "$ROOT" "root" + for fork_dir in local/sources/*/; do + [ -d "$fork_dir" ] || continue + fork_name=$(basename "$fork_dir") + [ -f "${fork_dir}Cargo.toml" ] || continue + regen_lockfile_for "${fork_dir%/}" "local/sources/${fork_name}" + done + + # Show diffs + changed=0 + echo "" + echo "--- Lockfile changes that would occur on a real bump ---" + for lock in "${!LOCK_BACKUPS[@]}"; do + backup="${LOCK_BACKUPS[$lock]}" + current="" + [ -f "$lock" ] && current=$(cat "$lock") + if [ "$backup" != "$current" ]; then + changed=$((changed + 1)) + label="${lock#$ROOT/}" + echo "" + echo " === $label ===" + diff <(echo "$backup") <(echo "$current") | head -30 + if [ $(diff <(echo "$backup") <(echo "$current") | wc -l) -gt 30 ]; then + echo " ... (truncated — $(( $(diff <(echo "$backup") <(echo "$current") | wc -l) - 30 )) more lines)" + fi + fi + done + + # Restore originals + for lock in "${!LOCK_BACKUPS[@]}"; do + echo "${LOCK_BACKUPS[$lock]}" > "$lock" + done + + echo "" + echo "=== Dry-run summary ===" + echo " Lockfiles with changes: $changed" + if [ $changed -eq 0 ]; then + echo " ALL CLEAR — no lockfile changes would occur. Safe to --regen." + else + echo " Review the diffs above. If acceptable, run: $0 --regen" + fi + exit 0 +fi + +echo "" +echo "=== Phase 3: Regenerate Cargo.lock files ===" + # Regenerate root cookbook lockfile regen_lockfile_for "$ROOT" "root"