feat(install): конфликтующие файлы бэкапятся перед stow для всех пакетов
- Зачем:
- конфликт с уже существующим конфигом приходилось разбирать руками:
stow печатал "All operations aborted" и откатывал все пакеты сразу.
- Что:
- backup_conflicting_kitty_files заменена на общую
backup_conflicting_stow_targets по списку AVAILABLE_PACKAGES.
- обход повторяет логику stow: отсутствующая цель и каталоги-симлинки
пропускаются вместе с поддеревом, поэтому свёрнутые пакеты
(~/.codex, ~/.vscode-server) не обходятся и не бэкапятся.
- в фикстуру тестов добавлен пакет wezterm, добавлены тесты на конфликт
в HOME и на неприкосновенность содержимого свёрнутого пакета.
- Проверка:
- bash tests/test_install.sh
This commit is contained in:
1 parent
6610adeac5
commit
c6d7e7eeb0
2 files changed
+83
-16
No files matched your search
+45
-15
@@ -169,12 +169,31 @@ migrate_claude_stow_symlink() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Убрать с пути чужие файлы конфигурации kitty: kitty создаёт их сам, и без
|
# Убрать с пути чужие файлы, которые конфликтуют со stow-пакетами.
|
||||||
# бэкапа stow отказывается ставить весь набор пакетов ("All operations
|
#
|
||||||
# aborted"), а не только kitty.
|
# Программы создают свои конфиги сами (kitty.conf, .wezterm.lua), и при
|
||||||
backup_conflicting_kitty_files() {
|
# конфликте stow не просто пропускает пакет, а печатает "All operations
|
||||||
local src_dir="$DOTFILES_DIR/kitty/.config/kitty"
|
# aborted" и откатывает весь набор: install.sh падает по pipefail, не дойдя
|
||||||
local dst_dir="$HOME/.config/kitty"
|
# до настройки claude, opencode и .bashrc. Раньше это чинилось руками.
|
||||||
|
#
|
||||||
|
# Обход повторяет логику stow, а не перебирает файлы подряд: если каталога в
|
||||||
|
# HOME нет, конфликта быть не может и всё поддерево пропускается. Это заодно
|
||||||
|
# отсекает свёрнутые пакеты (~/.codex, ~/.vscode-server) с их гигабайтами
|
||||||
|
# рантайм-данных.
|
||||||
|
backup_conflicting_stow_targets() {
|
||||||
|
local pkg=""
|
||||||
|
|
||||||
|
for pkg in "$@"; do
|
||||||
|
backup_conflicting_tree "$DOTFILES_DIR/$pkg" "$HOME" ""
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Рекурсивный обход одного уровня: src_dir — каталог пакета, dst_dir — его
|
||||||
|
# соответствие в HOME, rel — путь для сообщений.
|
||||||
|
backup_conflicting_tree() {
|
||||||
|
local src_dir="$1"
|
||||||
|
local dst_dir="$2"
|
||||||
|
local rel="$3"
|
||||||
local src=""
|
local src=""
|
||||||
local dst=""
|
local dst=""
|
||||||
local base=""
|
local base=""
|
||||||
@@ -182,18 +201,30 @@ backup_conflicting_kitty_files() {
|
|||||||
local dst_real=""
|
local dst_real=""
|
||||||
|
|
||||||
[ -d "$src_dir" ] || return 0
|
[ -d "$src_dir" ] || return 0
|
||||||
[ -d "$dst_dir" ] || return 0
|
|
||||||
[ ! -L "$dst_dir" ] || return 0
|
|
||||||
|
|
||||||
for src in "$src_dir"/*; do
|
for src in "$src_dir"/* "$src_dir"/.[!.]*; do
|
||||||
[ -e "$src" ] || continue
|
[ -e "$src" ] || [ -L "$src" ] || continue
|
||||||
base="$(basename "$src")"
|
base="$(basename "$src")"
|
||||||
dst="$dst_dir/$base"
|
dst="$dst_dir/$base"
|
||||||
|
|
||||||
# -L отдельно от -e: битый симлинк для stow тоже конфликт.
|
# Цели нет — stow создаст симлинк или свернёт каталог, конфликта нет.
|
||||||
[ -e "$dst" ] || [ -L "$dst" ] || continue
|
[ -e "$dst" ] || [ -L "$dst" ] || continue
|
||||||
|
|
||||||
# Симлинк на наш же файл — это результат прошлого прогона, не трогаем.
|
if [ -d "$src" ] && [ ! -L "$src" ]; then
|
||||||
|
# Каталог: симлинк в цели трогать нельзя — stow спускается внутрь,
|
||||||
|
# так работают и свёрнутые пакеты, и посторонний симлинк ~/.config.
|
||||||
|
[ ! -L "$dst" ] || continue
|
||||||
|
# Два настоящих каталога stow объединяет, конфликт может быть глубже.
|
||||||
|
if [ -d "$dst" ]; then
|
||||||
|
backup_conflicting_tree "$src" "$dst" "$rel/$base"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Файл на месте каталога — stow каталог поверх него не создаст.
|
||||||
|
backup_existing_path "$dst" "~$rel/$base"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Файл: симлинк на наш же файл — результат прошлого прогона.
|
||||||
if [ -L "$dst" ]; then
|
if [ -L "$dst" ]; then
|
||||||
src_real="$(readlink -f "$src" 2>/dev/null || true)"
|
src_real="$(readlink -f "$src" 2>/dev/null || true)"
|
||||||
dst_real="$(readlink -f "$dst" 2>/dev/null || true)"
|
dst_real="$(readlink -f "$dst" 2>/dev/null || true)"
|
||||||
@@ -202,7 +233,7 @@ backup_conflicting_kitty_files() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
backup_existing_path "$dst" "~/.config/kitty/$base"
|
backup_existing_path "$dst" "~$rel/$base"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,9 +409,8 @@ if [ -d "$HOME/.config" ] && [ ! -L "$HOME/.config" ] \
|
|||||||
mkdir -p "$HOME/.config/kitty"
|
mkdir -p "$HOME/.config/kitty"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
backup_conflicting_kitty_files
|
|
||||||
|
|
||||||
if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then
|
if [ "${#AVAILABLE_PACKAGES[@]}" -gt 0 ]; then
|
||||||
|
backup_conflicting_stow_targets "${AVAILABLE_PACKAGES[@]}"
|
||||||
stow_package "${AVAILABLE_PACKAGES[@]}"
|
stow_package "${AVAILABLE_PACKAGES[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+38
-1
@@ -37,7 +37,7 @@ setup_fixture() {
|
|||||||
# zed/.config/opencode и т.п.) весят гигабайты, переполняют /tmp и ломают
|
# zed/.config/opencode и т.п.) весят гигабайты, переполняют /tmp и ломают
|
||||||
# stow конфликтами, поэтому в тестовое дерево не попадает.
|
# stow конфликтами, поэтому в тестовое дерево не попадает.
|
||||||
git -C "$REPO_ROOT" ls-files -z --cached -- \
|
git -C "$REPO_ROOT" ls-files -z --cached -- \
|
||||||
install.sh bash tmux vim zed kitty vscode agents opencode codex gemini \
|
install.sh bash tmux vim zed kitty wezterm vscode agents opencode codex gemini \
|
||||||
claude proxy-client scripts \
|
claude proxy-client scripts \
|
||||||
| tar -C "$REPO_ROOT" --null -T - -cf - \
|
| tar -C "$REPO_ROOT" --null -T - -cf - \
|
||||||
| tar -xf - -C "$repo_root"
|
| tar -xf - -C "$repo_root"
|
||||||
@@ -200,6 +200,41 @@ test_rerun_does_not_backup_managed_kitty_config() {
|
|||||||
[ -z "$backup" ] || fail "Rerun must not create backups of its own symlinks: $backup"
|
[ -z "$backup" ] || fail "Rerun must not create backups of its own symlinks: $backup"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_existing_wezterm_config_is_backed_up() {
|
||||||
|
local fixture repo_root home_dir backup
|
||||||
|
fixture="$(setup_fixture)"
|
||||||
|
repo_root="$fixture/repo"
|
||||||
|
home_dir="$fixture/home"
|
||||||
|
mkdir -p "$home_dir"
|
||||||
|
# Тот же конфликт, но у пакета с файлом прямо в HOME, а не в ~/.config.
|
||||||
|
printf '%s\n' 'local wezterm = require("wezterm")' > "$home_dir/.wezterm.lua"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install.out"
|
||||||
|
|
||||||
|
[ -L "$home_dir/.wezterm.lua" ] || fail "Expected managed .wezterm.lua symlink"
|
||||||
|
backup="$(find "$home_dir" -maxdepth 1 -name '.wezterm.lua.bak.*' -print -quit)"
|
||||||
|
[ -n "$backup" ] || fail "Expected backup of the pre-existing .wezterm.lua"
|
||||||
|
assert_file_contains "$backup" 'require("wezterm")'
|
||||||
|
}
|
||||||
|
|
||||||
|
test_folded_package_runtime_files_are_not_backed_up() {
|
||||||
|
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"
|
||||||
|
# ~/.codex свёрнут в симлинк на пакет: файлы внутри — уже наши, не конфликт.
|
||||||
|
printf '%s\n' '{}' > "$home_dir/.codex/auth.json"
|
||||||
|
|
||||||
|
run_install "$repo_root" "$home_dir" env WSL_DISTRO_NAME= INSTALL_INTERACTIVE_MODE=never >"$fixture/install2.out"
|
||||||
|
|
||||||
|
backup="$(find "$repo_root" "$home_dir" -name '*.bak.*' -print -quit)"
|
||||||
|
[ -z "$backup" ] || fail "Folded package contents must not be backed up: $backup"
|
||||||
|
assert_file_contains "$home_dir/.codex/auth.json" "{}"
|
||||||
|
}
|
||||||
|
|
||||||
test_migrates_legacy_zed_config_symlink() {
|
test_migrates_legacy_zed_config_symlink() {
|
||||||
local fixture repo_root home_dir legacy_config output mode
|
local fixture repo_root home_dir legacy_config output mode
|
||||||
fixture="$(setup_fixture)"
|
fixture="$(setup_fixture)"
|
||||||
@@ -599,6 +634,8 @@ main() {
|
|||||||
test_existing_kitty_config_is_backed_up_and_does_not_abort_stow
|
test_existing_kitty_config_is_backed_up_and_does_not_abort_stow
|
||||||
test_foreign_kitty_config_symlink_is_backed_up
|
test_foreign_kitty_config_symlink_is_backed_up
|
||||||
test_rerun_does_not_backup_managed_kitty_config
|
test_rerun_does_not_backup_managed_kitty_config
|
||||||
|
test_existing_wezterm_config_is_backed_up
|
||||||
|
test_folded_package_runtime_files_are_not_backed_up
|
||||||
test_migrates_legacy_zed_config_symlink
|
test_migrates_legacy_zed_config_symlink
|
||||||
test_stops_legacy_migration_when_listing_fails
|
test_stops_legacy_migration_when_listing_fails
|
||||||
test_does_not_migrate_unrelated_config_symlink
|
test_does_not_migrate_unrelated_config_symlink
|
||||||
|
|||||||
Reference in new issue
Block a user