diff --git a/README.md b/README.md index 7244dae..d0b47e3 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,6 @@ Chezmoi использует отдельный source dir `chezmoi/` внутр | `vim` | Минимальный .vimrc без плагинов, 2 пробела, leader = `Space` | | `wezterm` | Общий для Linux и Windows конфиг: одинаковые оформление и горячие клавиши | | `kitty` | `kitty.conf` и тема; `~/.config/kitty` остаётся настоящим каталогом, чтобы файлы kitty не попадали в репозиторий | -| `zed` | Конфигурация Zed редактора | | `vscode` | `server-env-setup` для VS Code Server в WSL: секреты + авто-прокси | `install.sh` дополнительно настраивает opencode (симлинки, MCP-конфиг), claude (симлинкает `settings.local.json` из репозитория) и подключает bash_custom. diff --git a/install.sh b/install.sh index a0bd5d7..caa3eeb 100755 --- a/install.sh +++ b/install.sh @@ -169,6 +169,43 @@ migrate_claude_stow_symlink() { fi } +# Убрать с пути чужие файлы конфигурации kitty: kitty создаёт их сам, и без +# бэкапа stow отказывается ставить весь набор пакетов ("All operations +# aborted"), а не только kitty. +backup_conflicting_kitty_files() { + local src_dir="$DOTFILES_DIR/kitty/.config/kitty" + local dst_dir="$HOME/.config/kitty" + local src="" + local dst="" + local base="" + local src_real="" + local dst_real="" + + [ -d "$src_dir" ] || return 0 + [ -d "$dst_dir" ] || return 0 + [ ! -L "$dst_dir" ] || return 0 + + for src in "$src_dir"/*; do + [ -e "$src" ] || continue + base="$(basename "$src")" + dst="$dst_dir/$base" + + # -L отдельно от -e: битый симлинк для stow тоже конфликт. + [ -e "$dst" ] || [ -L "$dst" ] || continue + + # Симлинк на наш же файл — это результат прошлого прогона, не трогаем. + if [ -L "$dst" ]; then + src_real="$(readlink -f "$src" 2>/dev/null || true)" + dst_real="$(readlink -f "$dst" 2>/dev/null || true)" + if [ -n "$src_real" ] && [ "$src_real" = "$dst_real" ]; then + continue + fi + fi + + backup_existing_path "$dst" "~/.config/kitty/$base" + done +} + # Миграция: раскрыть старую stow-ссылку ~/.config -> zed/.config. # В пакете zed остаётся только управляемый каталог zed; остальные настройки # возвращаются в домашний каталог и больше не попадают в репозиторий. @@ -341,6 +378,8 @@ if [ -d "$HOME/.config" ] && [ ! -L "$HOME/.config" ] \ mkdir -p "$HOME/.config/kitty" fi +backup_conflicting_kitty_files + if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then stow_package "${AVAILABLE_PACKAGES[@]}" fi diff --git a/tests/test_install.sh b/tests/test_install.sh index f91795b..b511527 100755 --- a/tests/test_install.sh +++ b/tests/test_install.sh @@ -143,6 +143,63 @@ test_fresh_install_does_not_fold_config_into_kitty() { assert_not_exists "$repo_root/kitty/.config/kitty/kitty.conf.bak" } +test_existing_kitty_config_is_backed_up_and_does_not_abort_stow() { + local fixture repo_root home_dir backup + fixture="$(setup_fixture)" + repo_root="$fixture/repo" + home_dir="$fixture/home" + mkdir -p "$home_dir/.config/kitty" + # kitty создаёт конфиг сам, поэтому на чужой машине файл уже существует. + printf '%s\n' 'font_size 14' > "$home_dir/.config/kitty/kitty.conf" + + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out" + + [ -L "$home_dir/.config/kitty/kitty.conf" ] || fail "Expected managed kitty.conf symlink" + backup="$(find "$home_dir/.config/kitty" -maxdepth 1 -name 'kitty.conf.bak.*' -print -quit)" + [ -n "$backup" ] || fail "Expected backup of the pre-existing kitty.conf" + assert_file_contains "$backup" "font_size 14" + # Конфликт в одном пакете не должен обрывать stow целиком. + [ -L "$home_dir/.vimrc" ] || fail "Expected other packages to be stowed as well" + [ -L "$home_dir/.bash_custom" ] || fail "Expected install to continue past stow" +} + +test_foreign_kitty_config_symlink_is_backed_up() { + local fixture repo_root home_dir backup target + fixture="$(setup_fixture)" + repo_root="$fixture/repo" + home_dir="$fixture/home" + mkdir -p "$home_dir/.config/kitty" "$fixture/elsewhere" + # Чужой симлинк для stow такой же конфликт, как обычный файл. + printf '%s\n' 'font_size 99' > "$fixture/elsewhere/my-kitty.conf" + ln -s "$fixture/elsewhere/my-kitty.conf" "$home_dir/.config/kitty/kitty.conf" + + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out" + + target="$(readlink -f "$home_dir/.config/kitty/kitty.conf")" + [ "$target" = "$(readlink -f "$repo_root/kitty/.config/kitty/kitty.conf")" ] \ + || fail "Expected managed kitty.conf, got $target" + backup="$(find "$home_dir/.config/kitty" -maxdepth 1 -name 'kitty.conf.bak.*' -print -quit)" + [ -n "$backup" ] || fail "Expected backup of the foreign kitty.conf symlink" + [ -L "$home_dir/.vimrc" ] || fail "Expected other packages to be stowed as well" + # Исходный файл за симлинком остаётся нетронутым. + assert_file_contains "$fixture/elsewhere/my-kitty.conf" "font_size 99" +} + +test_rerun_does_not_backup_managed_kitty_config() { + local fixture repo_root home_dir backup + fixture="$(setup_fixture)" + repo_root="$fixture/repo" + home_dir="$fixture/home" + mkdir -p "$home_dir" + + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out" + run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install2.out" + + [ -L "$home_dir/.config/kitty/kitty.conf" ] || fail "Expected managed kitty.conf symlink" + backup="$(find "$home_dir/.config/kitty" -maxdepth 1 -name '*.bak.*' -print -quit)" + [ -z "$backup" ] || fail "Rerun must not create backups of its own symlinks: $backup" +} + test_migrates_legacy_zed_config_symlink() { local fixture repo_root home_dir legacy_config output mode fixture="$(setup_fixture)" @@ -539,6 +596,9 @@ main() { test_fresh_install_uses_home_target test_fresh_install_does_not_fold_config_into_kitty + test_existing_kitty_config_is_backed_up_and_does_not_abort_stow + test_foreign_kitty_config_symlink_is_backed_up + test_rerun_does_not_backup_managed_kitty_config test_migrates_legacy_zed_config_symlink test_stops_legacy_migration_when_listing_fails test_does_not_migrate_unrelated_config_symlink