Something went wrong. Try again.
🏠 sweet ~/
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#!/bin/bash## script/stow-package <package>## Wrapper around `stow --restow --no-folding` that handles pre-existing files# in $HOME by moving them into a timestamped backup dir before stowing.# Intended for the first-time install / migration case where $HOME already# contains real config files or foreign symlinks (from a previous dotfile# manager or hand-authored) that would otherwise make stow abort.## Behavior:# 1. Scans the package for target paths in $HOME that are already occupied.# 2. Moves every conflicting real file into# ~/.dotfiles-backup/<timestamp>/<relative-path>/, preserving structure.# 3. Runs the real stow.## Conflicting user files are never deleted — if the new setup is wrong you can# rsync the backup dir back over $HOME. Restowing only removes obsolete# symlinks owned by this package. Invoked by the Makefile's stow targets and# by script/install-darwin.
set -euo pipefail
pkg="${1:?usage: script/stow-package <package>}"
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"cd "$REPO_ROOT"
if [ ! -d "$pkg" ]; then echo "stow-package: no such package: $pkg" >&2 exit 1fi
# Enumerate conflicts by inspecting the filesystem directly. With# --no-folding every file in the package becomes its own symlink in $HOME,# so a conflict is simply: something already occupies a target path and it# isn't what we're about to put there. Deriving this ourselves keeps the# script independent of stow's human-readable diagnostics, whose wording# differs between stow 2.3.x (Debian/Ubuntu) and 2.4+ (Homebrew) and is not# a stable interface in any case.conflicts=$( find "$pkg" -mindepth 1 -print0 | while IFS= read -r -d '' src; do rel="${src#"$pkg"/}" target="$HOME/$rel" # Stow always ignores its package-local ignore file. Runtime paths in # that file are mirrored in the repository's .gitignore, so skip them # here too instead of moving aside host-local state Stow will not link. [ "$rel" = ".stow-local-ignore" ] && continue git check-ignore -q -- "$src" 2>/dev/null && continue # -e is false for a dangling symlink, so test -L too: a broken link # still occupies the path and still has to be moved aside. [ -e "$target" ] || [ -L "$target" ] || continue if [ -d "$src" ] && [ ! -L "$src" ]; then if [ -L "$target" ]; then # An owned folded directory is safe: --restow combined with # --no-folding will replace it with real target directories. # A link to any other directory is a migration conflict. [ "$target" -ef "$src" ] || printf '%s\n' "$rel" else # stow just needs a real directory here; anything else blocks it. [ -d "$target" ] || printf '%s\n' "$rel" fi else # -ef compares device+inode through symlinks, so this is true # when the target is already the very file the package provides # — a re-stow, not a conflict. [ "$target" -ef "$src" ] || printf '%s\n' "$rel" fi done)
if [ -n "$conflicts" ]; then ts=$(date +%Y%m%d-%H%M%S) backup_root="$HOME/.dotfiles-backup/$ts" echo "stow-package: $pkg has conflicts; moving aside to $backup_root" while IFS= read -r rel; do [ -n "$rel" ] || continue src="$HOME/$rel" dst="$backup_root/$rel" mkdir -p "$(dirname "$dst")" mv "$src" "$dst" echo " moved $rel" done <<< "$conflicts"fi
stow --restow --no-folding -t "$HOME" "$pkg"echo "stow-package: stowed $pkg"