Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ Running `./sync.sh` updates only those components:
./sync.sh --all --dry-run
```

Machines configured before v1.2 are migrated automatically. If no state file
exists, `sync.sh` first detects configs already linked to this repository. When
none can be detected, it preserves the original behavior by syncing the legacy
Shell, WezTerm, Neovim, and AeroSpace config set. Every successful sync also
reinstalls this repository's Git hooks before migrated component state is saved.

## 🧩 Adding software

Components are auto-discovered from `components/*.sh`. A normal Homebrew formula
Expand Down
52 changes: 52 additions & 0 deletions lib/core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,58 @@ pm_sync_declared_configs() {
done <<< "$configs"
}

pm_paths_match() {
local first="$1"
local second="$2"
local first_dir second_dir first_path second_path

first_dir="$(cd "$(dirname "$first")" 2>/dev/null && pwd -P)" || return 1
second_dir="$(cd "$(dirname "$second")" 2>/dev/null && pwd -P)" || return 1
first_path="$first_dir/$(basename "$first")"
second_path="$second_dir/$(basename "$second")"
[ "$first_path" = "$second_path" ]
}

pm_symlink_points_to() {
local destination="$1"
local expected_source="$2"
[ -L "$destination" ] || return 1

local link_target
link_target="$(readlink "$destination")" || return 1
case "$link_target" in
/*) ;;
*) link_target="$(dirname "$destination")/$link_target" ;;
esac
pm_paths_match "$link_target" "$expected_source"
}

pm_component_has_managed_config() {
local id="$1"
local configs line source_path destination
configs="$(pm_component_field "$id" COMPONENT_CONFIGS)"
[ -n "$configs" ] || return 1

while IFS= read -r line; do
[ -z "$line" ] && continue
IFS='|' read -r source_path destination <<< "$line"
if pm_symlink_points_to "$HOME/$destination" "$POWER_MAC_ROOT/$source_path"; then
return 0
fi
done <<< "$configs"
return 1
}

pm_detect_legacy_components() {
local id
while IFS= read -r id; do
[ -n "$id" ] || continue
if pm_component_has_managed_config "$id"; then
printf '%s\n' "$id"
fi
done < <(pm_selectable_component_ids)
}

pm_install_brew_component() {
local id="$1"
local kind package installed_name
Expand Down
42 changes: 38 additions & 4 deletions sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Usage:
./sync.sh --components ID,ID,... [--dry-run]

Without selection flags, sync.sh reads the components recorded by install.sh.
If no state exists, it detects configs linked by older power_mac releases and
migrates them after a successful sync.

Options:
--all Sync every selectable component configuration.
Expand Down Expand Up @@ -52,7 +54,10 @@ done

pm_load_components
SELECTED_COMPONENTS=()
if pm_load_state; then
STATE_LOADED=false
MIGRATE_STATE=false
if pm_load_state && [ "${#PM_STATE_COMPONENTS[@]}" -gt 0 ]; then
STATE_LOADED=true
POWER_MAC_TMUX_STYLE="$PM_STATE_TMUX_STYLE"
fi

Expand All @@ -66,10 +71,21 @@ case "$SELECTION_MODE" in
pm_split_csv "$COMPONENTS_ARGUMENT" SELECTED_COMPONENTS
;;
state)
if [ "${#PM_STATE_COMPONENTS[@]}" -eq 0 ]; then
pm_die "No saved component state. Run ./install.sh or use sync.sh --all/--components."
if [ "$STATE_LOADED" = true ]; then
SELECTED_COMPONENTS=("${PM_STATE_COMPONENTS[@]}")
else
while IFS= read -r id; do
[ -n "$id" ] && SELECTED_COMPONENTS+=("$id")
done < <(pm_detect_legacy_components)

if [ "${#SELECTED_COMPONENTS[@]}" -gt 0 ]; then
pm_warn "No saved state found; detected legacy power_mac configs: $(pm_join_by , "${SELECTED_COMPONENTS[@]}")"
else
SELECTED_COMPONENTS=(shell wezterm neovim aerospace)
pm_warn "No saved state or managed symlinks found; using the legacy sync configuration"
fi
MIGRATE_STATE=true
Comment thread
felipe0328 marked this conversation as resolved.
fi
SELECTED_COMPONENTS=("${PM_STATE_COMPONENTS[@]}")
;;
esac

Expand All @@ -93,4 +109,22 @@ if [ "${#FAILURES[@]}" -gt 0 ]; then
exit 1
fi

pm_step "Repository setup"
if [ "$POWER_MAC_DRY_RUN" = true ]; then
pm_ok "[dry-run] Would install this repository's Git hooks"
elif bash "$SCRIPT_DIR/scripts/install-hooks.sh"; then
pm_ok "Git hooks installed"
else
pm_error "Configs synced, but Git hooks could not be installed"
exit 1
fi

if [ "$MIGRATE_STATE" = true ]; then
if pm_save_state "$POWER_MAC_TMUX_STYLE" "${SELECTED_COMPONENTS[@]}"; then
pm_ok "Saved detected components for future syncs"
else
pm_warn "Configs synced, but component state could not be saved"
fi
fi

printf '\n%sSelected configurations are in sync.%s\n\n' "$PM_GREEN" "$PM_RESET"
60 changes: 57 additions & 3 deletions tests/installer_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ EOF
cat > "$directory/git" <<'EOF'
#!/usr/bin/env bash
printf 'git %s\n' "$*" >> "${POWER_MAC_TEST_LOG:?}"
if [ "${POWER_MAC_FAIL_HOOKS:-false}" = true ] &&
[ "$1" = -C ] &&
[ "$3" = config ] &&
[ "$4" = core.hooksPath ]; then
exit 1
fi
if [ "$1" = clone ]; then
destination="${@: -1}"
mkdir -p "$destination/.git"
Expand Down Expand Up @@ -119,6 +125,7 @@ run_sync() {
HOME="$home" \
PATH="$TEST_TMP/fake-bin:$PATH" \
POWER_MAC_TEST_LOG="$TEST_TMP/commands.log" \
POWER_MAC_FAIL_HOOKS="${POWER_MAC_FAIL_HOOKS:-false}" \
"$ROOT/sync.sh" "$@"
}

Expand Down Expand Up @@ -179,12 +186,59 @@ else
fail "saved-state sync touches only recorded component configs"
fi

legacy_home="$TEST_TMP/home-legacy-detected"
mkdir -p "$legacy_home/.config/wezterm" "$legacy_home/.config"
ln -s "$ROOT/wezterm.lua" "$legacy_home/.config/wezterm/wezterm.lua"
ln -s "$ROOT/nvim" "$legacy_home/.config/nvim"
: > "$TEST_TMP/commands.log"
output="$(run_sync "$legacy_home")"
if assert_contains "$output" "detected legacy power_mac configs: wezterm,neovim" &&
assert_file_contains "$legacy_home/.config/power_mac/state" "components=wezterm,neovim" &&
assert_file_contains "$TEST_TMP/commands.log" "config core.hooksPath .githooks" &&
[ ! -e "$legacy_home/.zshrc" ] &&
[ ! -e "$legacy_home/.aerospace.toml" ]; then
pass "legacy migration reinstalls hooks before saving state"
else
fail "legacy migration reinstalls hooks before saving state"
fi

empty_home="$TEST_TMP/home-no-state"
mkdir -p "$empty_home"
if run_sync "$empty_home" >/dev/null 2>&1; then
fail "sync without state or explicit selection is rejected"
output="$(run_sync "$empty_home")"
if assert_contains "$output" "using the legacy sync configuration" &&
[ -L "$empty_home/.zshrc" ] &&
[ -L "$empty_home/.config/wezterm/wezterm.lua" ] &&
[ -L "$empty_home/.config/nvim" ] &&
[ -L "$empty_home/.aerospace.toml" ] &&
[ ! -e "$empty_home/.tmux.conf" ] &&
assert_file_contains "$empty_home/.config/power_mac/state" "components=shell,wezterm,neovim,aerospace"; then
pass "sync without state falls back to the legacy config set"
else
fail "sync without state falls back to the legacy config set"
fi

dry_migration_home="$TEST_TMP/home-legacy-dry"
mkdir -p "$dry_migration_home/.config/wezterm"
ln -s "$ROOT/wezterm.lua" "$dry_migration_home/.config/wezterm/wezterm.lua"
: > "$TEST_TMP/commands.log"
output="$(run_sync "$dry_migration_home" --dry-run)"
if assert_contains "$output" "Would install this repository's Git hooks" &&
[ ! -e "$dry_migration_home/.config/power_mac/state" ] &&
[ ! -s "$TEST_TMP/commands.log" ]; then
pass "legacy migration dry-run reports hooks without side effects"
else
fail "legacy migration dry-run reports hooks without side effects"
fi

failed_hooks_home="$TEST_TMP/home-legacy-hook-failure"
mkdir -p "$failed_hooks_home/.config/wezterm"
ln -s "$ROOT/wezterm.lua" "$failed_hooks_home/.config/wezterm/wezterm.lua"
if POWER_MAC_FAIL_HOOKS=true run_sync "$failed_hooks_home" >/dev/null 2>&1; then
fail "hook failure makes legacy migration fail"
elif [ ! -e "$failed_hooks_home/.config/power_mac/state" ]; then
pass "hook failure prevents migrated state persistence"
else
pass "sync without state or explicit selection is rejected"
fail "hook failure prevents migrated state persistence"
fi

home="$TEST_TMP/home-backup"
Expand Down