ask before replacing a config the user already has, and show the difference

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 <file>.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) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 20:48:08 +00:00
co-authored by Claude Opus 5
parent 2b9e16c11e
commit 2e70a6dd21
+52 -12
View File
@@ -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
if [[ ! -f "$dest" ]]; then
install -D -m 0644 -o "$owner" -g "$owner" "$src" "$dest"
return 0
fi
warn "kept your ${dest} — it differs from the one shipped here"
echo " to take ours instead: cp ${src} ${dest}"
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
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.