#!/usr/bin/env bash # reconcile-hyde.sh — reconcile a HyDE update with your dots-tracked configs. # # HyDE's updater (`Scripts/install.sh -r`) overwrites every config listed in # Scripts/restore_cfg.psv — including ~/.config/hypr/*. It backs the old ones up to # ~/.config/cfg_backups and REPLACES your stow symlinks with real files (HyDE's new # defaults). This tool, run ON THE GUI MACHINE right after that update, shows per file: # your dots version vs HyDE's new one # lets you keep / take / merge each, then removes the severed real files and re-runs # install.sh so everything is symlinked back to the repo. # # ./bin/reconcile-hyde.sh [host] report only — safe, changes nothing (default) # ./bin/reconcile-hyde.sh -i [host] interactive: decide each conflicting file # ./bin/reconcile-hyde.sh --relink [host] skip decisions: back up severed files, # then re-stow (repo versions win) # # host defaults to `hostname -s`. Commit your repo first so choices are reviewable/revertable. set -uo pipefail # Config subtrees where HyDE and your dots overlap. Add/remove as your setup needs. SUBTREES=(.config/hypr .config/waybar) # --- args --- MODE=report HOST="" for a in "$@"; do case "$a" in -i|--interactive) MODE=interactive ;; --relink) MODE=relink ;; -h|--help) awk 'NR > 1 && !/^#/ { exit } NR > 1' "$0"; exit 0 ;; -*) echo "unknown option: $a" >&2; exit 2 ;; *) HOST="$a" ;; esac done [ -z "$HOST" ] && HOST="$(hostname -s)" skip_relink=() # files the user chose to skip during interactive reconcile DOTS="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)" # repo root (script in bin/) [ -x "$DOTS/install.sh" ] || { echo "error: $DOTS/install.sh not found — run from inside the dots repo" >&2; exit 1; } # repo package search order: host overlay wins, then gui, then common PKGS=("$HOST" gui common) repo_src() { # repo_src -> print the repo file stow would link, or nothing local rel="$1" p for p in "${PKGS[@]}"; do [ -f "$DOTS/$p/$rel" ] && { printf '%s\n' "$DOTS/$p/$rel"; return 0; } done return 1 } echo "host=$HOST dots=$DOTS mode=$MODE" if [ -n "$(git -C "$DOTS" status --porcelain 2>/dev/null)" ]; then echo "note: $DOTS has uncommitted changes — commit first so reconcile choices are reviewable." >&2 fi [ -d "$HOME/.config/cfg_backups" ] && echo "HyDE's pre-update backups are in ~/.config/cfg_backups" echo # --- classify every live file under the managed subtrees --- linked=0; foreign=0 same=(); conflicts=(); new_files=() for st in "${SUBTREES[@]}"; do [ -d "$HOME/$st" ] || continue while IFS= read -r -d '' live; do rel="${live#"$HOME"/}" if [ -L "$live" ]; then # still a symlink tgt="$(readlink -f "$live" 2>/dev/null || true)" case "$tgt" in "$DOTS"/*) linked=$((linked+1)) ;; # intact -> HyDE didn't sever it *) foreign=$((foreign+1)); echo " ? $rel -> symlink outside repo: $tgt" ;; esac continue fi # real file: HyDE overwrote (severed) it, or it is brand new src="$(repo_src "$rel" || true)" if [ -z "$src" ]; then new_files+=("$rel") elif diff -q "$src" "$live" >/dev/null 2>&1; then same+=("$rel") # content == repo; just relink else conflicts+=("$rel") # HyDE's new content differs from yours fi done < <(find "$HOME/$st" \( -type f -o -type l \) -print0) done echo "== summary ==" echo " intact symlinks (untouched): $linked" echo " severed but identical (relink): ${#same[@]}" echo " severed & CHANGED (need decision): ${#conflicts[@]}" echo " new HyDE files not tracked by dots: ${#new_files[@]}" [ "$foreign" -gt 0 ] && echo " foreign symlinks flagged above: $foreign" echo if [ "$linked" -gt 0 ]; then echo "For intact symlinks, check whether HyDE wrote THROUGH them into the repo:" echo " git -C $DOTS diff -- '*/.config/hypr/*' '*/.config/waybar/*'" echo fi if [ "${#new_files[@]}" -gt 0 ]; then echo "New HyDE files (left as-is; add to a package + re-stow if you want them tracked):" printf ' %s\n' "${new_files[@]}" echo fi if [ "${#conflicts[@]}" -gt 0 ]; then echo "Changed files (your dots version vs HyDE's new default):" for rel in "${conflicts[@]}"; do src="$(repo_src "$rel")" printf ' %-45s %s changed lines\n' "$rel" "$(diff "$src" "$HOME/$rel" | grep -cE '^[<>]')" done echo fi # report-only ends here if [ "$MODE" = report ]; then if [ "${#conflicts[@]}" -gt 0 ]; then echo "Re-run with -i to decide each changed file, or --relink to keep your repo versions wholesale." elif [ "${#same[@]}" -gt 0 ]; then echo "No content conflicts. Re-run with --relink to drop the severed files and re-symlink." else echo "Nothing to reconcile." fi exit 0 fi # --- interactive: decide each changed file, writing the choice into the repo copy --- if [ "$MODE" = interactive ] && [ "${#conflicts[@]}" -gt 0 ]; then for rel in "${conflicts[@]}"; do src="$(repo_src "$rel")"; live="$HOME/$rel" echo "──────── $rel ────────" echo " repo (yours): ${src#"$DOTS"/}" diff "$src" "$live" | sed 's/^/ /' | head -60 echo " [k]eep yours (default) [t]ake HyDE's [m]erge in \$EDITOR -d [s]kip" read -r -p " choice> " ans took HyDE's version into $src" ;; m|merge) "${EDITOR:-nvim}" -d "$src" "$live" /dev/tty 2>&1; echo " -> merged (saved $src)" ;; s|skip) echo " -> skipped (left severed; not relinked)"; skip_relink+=("$rel") ;; *) echo " -> kept your repo version" ;; esac done echo fi # --- relink: back up severed files that the repo owns, remove them, re-stow --- BK="$HOME/.config/hyde-reconcile-backup-$(date +%Y%m%d-%H%M%S)" to_relink=() for st in "${SUBTREES[@]}"; do [ -d "$HOME/$st" ] || continue while IFS= read -r -d '' live; do [ -L "$live" ] && continue # only real (severed) files rel="${live#"$HOME"/}" repo_src "$rel" >/dev/null || continue # only files the repo can provide for s in "${skip_relink[@]}"; do [ "$s" = "$rel" ] && continue 2; done to_relink+=("$rel") done < <(find "$HOME/$st" -type f -print0) done if [ "${#to_relink[@]}" -eq 0 ]; then echo "nothing to relink."; exit 0 fi echo "will back up + remove ${#to_relink[@]} severed file(s), then re-stow so they link to the repo:" printf ' %s\n' "${to_relink[@]}" read -r -p "proceed? [y/N] " go