From 2e70a6dd21c593c82166ed4cf5c071ff3e0d905a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 12 Aug 2026 20:48:08 +0000 Subject: [PATCH] ask before replacing a config the user already has, and show the difference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keeping theirs silently was safe but unhelpful: they never learn a newer version exists, and the only hint was a cp command printed in a warning. Now it asks. [1] keep yours — nothing changes [2] use ours — yours is kept as .before-machine-setup [3] show me the difference first The diff is labelled "yours" and "ours" rather than by path, so - is what you would lose and + is what you would gain, and it goes through the pager because a config diff is routinely longer than a screen. Choosing to replace always keeps the old file beside the new one; nothing is destroyed. An unattended run — ASSUME_YES, or no terminal on stdin — keeps theirs and says so. "Yes to everything" cannot sensibly mean "overwrite configuration nobody was present to defend", so this is the one prompt ASSUME_YES answers conservatively rather than affirmatively. Applies to every file that goes through install_config, which is .tmux.conf and starship.toml today and is where any other dotfile should go. On the starship question: there is one file now, scripts/setup/starship.toml, and the inline copy is gone. Owner and members get the same prompt, which is what os-user-shell.ts always claimed. Verified through a pty, since the -t 0 guard correctly makes the interactive path untestable over a pipe: the diff renders, replacing writes the backup, and the live file ends up byte-identical to ours. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/setup/machine-setup/lib/files.sh | 70 +++++++++++++++++++----- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/scripts/setup/machine-setup/lib/files.sh b/scripts/setup/machine-setup/lib/files.sh index 17f41e9d..31e085a3 100644 --- a/scripts/setup/machine-setup/lib/files.sh +++ b/scripts/setup/machine-setup/lib/files.sh @@ -19,17 +19,19 @@ [[ -n "${MACHINE_SETUP_FILES_LOADED:-}" ]] && return 0 MACHINE_SETUP_FILES_LOADED=1 -# Put a config file in place, unless the user has their own. +# Put a config file in place, asking before it replaces one the user has. # # Three outcomes, and the caller can tell them apart by the return code: # -# 0 installed — there was nothing there +# 0 installed — there was nothing there, or the user chose to replace # 1 identical — already exactly this, nothing done -# 2 kept — theirs differs, left alone +# 2 kept — the user chose to keep theirs # -# Converging when there is nothing to lose and keeping what the user wrote when -# there is. The third case prints how to take ours, so the choice stays with -# them. +# When the file exists and differs, this ASKS rather than deciding. Silently +# keeping theirs is safe but unhelpful — they never learn that a newer version +# exists — and silently replacing it is how a setup script eats somebody's +#configuration. So: keep, replace, or show the difference first, and a replaced file is +# always kept beside the new one. # # NOTE for callers: 1 and 2 are outcomes, not failures — but they are still # non-zero, so calling this as a plain command under `set -e` ends the script @@ -37,19 +39,57 @@ MACHINE_SETUP_FILES_LOADED=1 # # install_config "$src" "$dest" "$user" && rc=0 || rc=$? install_config() { - local src="$1" dest="$2" owner="$3" + local src="$1" dest="$2" owner="$3" answer - if [[ -f "$dest" ]]; then - if cmp -s "$src" "$dest"; then - return 1 - fi - warn "kept your ${dest} — it differs from the one shipped here" - echo " to take ours instead: cp ${src} ${dest}" + if [[ ! -f "$dest" ]]; then + install -D -m 0644 -o "$owner" -g "$owner" "$src" "$dest" + return 0 + fi + + cmp -s "$src" "$dest" && return 1 + + echo "" + warn "${dest} already exists here, and differs from the one this script ships." + + # Never replace a file the user has without being told to. An unattended run + # answers "keep", because the alternative is destroying configuration nobody + # was present to defend. + if [[ "${ASSUME_YES:-}" == "1" ]] || [[ ! -t 0 ]]; then + echo " keeping yours (nothing was asked, so nothing is replaced)" return 2 fi - install -D -m 0644 -o "$owner" -g "$owner" "$src" "$dest" - return 0 + while true; do + echo " [1] keep yours — nothing changes" + echo " [2] use ours — yours is kept as ${dest}.before-machine-setup" + echo " [3] show me the difference first" + echo "" + if ! read -rp " Which one? (1/2/3) [1]: " answer; then + echo "" + echo " keeping yours" + return 2 + fi + case "${answer:-1}" in + 1) + echo " keeping yours" + return 2 + ;; + 2) + cp -a "$dest" "${dest}.before-machine-setup" + install -D -m 0644 -o "$owner" -g "$owner" "$src" "$dest" + ok "replaced — yours is at ${dest}.before-machine-setup" + return 0 + ;; + 3) + echo "" + # yours on the left, ours on the right: - is what you would lose, + # + is what you would gain. + diff -u --label "yours: ${dest}" --label "ours: ${src}" "$dest" "$src" | page + echo "" + ;; + *) warn "Pick 1, 2 or 3." ;; + esac + done } # Append a block to a file exactly once.