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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- Docs: the keybinding reference describes the Lua configuration instead of the removed `userprefs.conf`, documents binds that were missing from it, corrects four descriptions that no longer matched the code, and explains why an override needs the original bind's flags
- Docs: the shipped `hypr/hyprland.lua` stub shows a working bind instead of pointing at a wiki that does not exist
- Docs: the keybinding links in the German, Arabic, French, Dutch and Turkish readmes resolve again
- Core: every migration that has not been applied yet now runs in version order and is recorded, instead of only the newest one running and retiring the rest unseen
- Core: app launchers no longer show a false error when an unrelated `DEBUG` variable contains a non-boolean value such as `release`
- Desktop: the generated battery notification startup command now launches `batterynotify.lua` instead of the removed shell implementation
- Hyprland: Lua keybinds again match the documented shortcuts for window management, screenshots, wallpapers, Waybar, selectors, workspaces and the scratchpad
Expand Down
35 changes: 35 additions & 0 deletions Scripts/global_fn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,38 @@ print_log() {
cat
fi
}

# Runs each migration in "$1" missing from the record in "$2", in version order,
# and records the ones that exit zero. Migrations must therefore be safe to run
# on a machine that has no record yet, which replays all of them once.
run_pending_migrations() {
local migrationDir="$1"
local stateFile="$2"
local migrationFile
local applied=0
local pending=0

[ -d "${migrationDir}" ] || return 0
find "${migrationDir}" -maxdepth 1 -type f | grep -q . || return 0

mkdir -p "$(dirname "${stateFile}")"
[ -f "${stateFile}" ] || : >"${stateFile}"

while read -r migrationFile; do
[ -n "${migrationFile}" ] || continue
grep -qxF "${migrationFile}" "${stateFile}" && continue
pending=$((pending + 1))
echo "Found migration file: ${migrationFile}"
# stdin is closed for the migration: inheriting the loop's stdin let one
# that reads input swallow the names of every migration after it.
if sh "${migrationDir}/${migrationFile}" </dev/null; then
printf '%s\n' "${migrationFile}" >>"${stateFile}"
applied=$((applied + 1))
else
print_log -warn "Migration" "Failed to execute ${migrationFile}"
fi
done < <(find "${migrationDir}" -maxdepth 1 -type f -printf '%f\n' | sort -V)

[ "${pending}" -gt 0 ] || echo "No outstanding migrations in ${migrationDir}."
return 0
}
11 changes: 2 additions & 9 deletions Scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,9 @@ if has_operation "restore"; then

echo "Running migrations from: ${migrationDir}"

if [ -d "${migrationDir}" ] && find "${migrationDir}" -type f | grep -q .; then
migrationFile=$(find "${migrationDir}" -maxdepth 1 -type f -printf '%f\n' | sort -r | head -n 1)
migrationStateFile="${XDG_STATE_HOME:-${HOME}/.local/state}/hyde/migration/applied"

if [[ -n "${migrationFile}" && -f "${migrationDir}/${migrationFile}" ]]; then
echo "Found migration file: ${migrationFile}"
sh "${migrationDir}/${migrationFile}" || { true && print_log -warn "Migration" "Failed to execute ${migrationFile}"; }
else
echo "No migration file found in ${migrationDir}. Skipping migrations."
fi
fi
run_pending_migrations "${migrationDir}" "${migrationStateFile}"

fi

Expand Down
2 changes: 1 addition & 1 deletion Scripts/migrations/v26.4.3.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh

echo "Reloading hyprlock state"
hyde-shell hyprlock --reload
2 changes: 1 addition & 1 deletion Scripts/migrations/v26.7.4.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh

# The Lua release replaced a set of shell helpers with Lua and Python
# equivalents. Deployment overwrites files but does not delete the ones that
Expand Down
101 changes: 101 additions & 0 deletions tests/test_migrations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Migrations have to run in version order, once each, and record what succeeded.

. "$(dirname -- "$0")/lib/common.sh"

# shellcheck source=/dev/null
if ! . "$REPO_ROOT/Scripts/global_fn.sh" 2>/dev/null; then
fail "unable to source global_fn.sh"
finish
fi

if ! command -v run_pending_migrations >/dev/null 2>&1 && ! type run_pending_migrations >/dev/null 2>&1; then
fail "run_pending_migrations is not defined in global_fn.sh"
finish
fi

work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT

migration_dir="$work_dir/migrations"
state_file="$work_dir/state/applied"
order_log="$work_dir/order.log"
mkdir -p "$migration_dir"

for version in v25.9.1 v26.10.1; do
printf '#!/usr/bin/env sh\nprintf "%%s\\n" "%s" >>"%s"\n' "$version" "$order_log" \
>"$migration_dir/$version.sh"
done
# Reads stdin: inheriting the runner's stdin would let it swallow the names of
# the migrations queued after it.
printf '#!/usr/bin/env sh\ncat >/dev/null\nprintf "%%s\\n" "v26.4.3" >>"%s"\n' "$order_log" \
>"$migration_dir/v26.4.3.sh"
failure_log="$work_dir/failures.log"
printf '#!/usr/bin/env sh\nprintf "%%s\\n" "ran" >>"%s"\nexit 1\n' "$failure_log" \
>"$migration_dir/v26.11.0.sh"
chmod +x "$migration_dir"/*.sh

run_pending_migrations "$migration_dir" "$state_file" >/dev/null 2>&1

expected_order='v25.9.1
v26.4.3
v26.10.1'
actual_order=$(cat "$order_log" 2>/dev/null || true)
[ "$actual_order" = "$expected_order" ] ||
fail "migrations ran out of version order: $(echo "$actual_order" | tr '\n' ' ')"

for version in v25.9.1 v26.4.3 v26.10.1; do
grep -qxF "$version.sh" "$state_file" ||
fail "$version.sh succeeded but was not recorded as applied"
done

grep -qxF 'v26.11.0.sh' "$state_file" &&
fail "a migration that exited non-zero was recorded as applied"

: >"$order_log"
run_pending_migrations "$migration_dir" "$state_file" >/dev/null 2>&1

[ -s "$order_log" ] &&
fail "already applied migrations ran a second time: $(tr '\n' ' ' <"$order_log")"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

failure_runs=$(wc -l <"$failure_log" 2>/dev/null || echo 0)
[ "$failure_runs" -eq 2 ] ||
fail "a failed migration ran $failure_runs time(s) across two passes, expected 2"

second_pass=$(run_pending_migrations "$migration_dir" "$state_file" 2>&1 || true)
case $second_pass in
*"No outstanding migrations"*)
fail "a migration was still pending but the run reported none outstanding"
;;
esac

# A mention in a comment is not a call: the name has to sit where a command goes.
grep -qE '^[[:space:]]*run_pending_migrations[[:space:]]+' "$REPO_ROOT/Scripts/install.sh" ||
fail "install.sh does not invoke run_pending_migrations as a command"

missing_dir_output=$(run_pending_migrations "$work_dir/absent" "$state_file" 2>&1)
[ -z "$missing_dir_output" ] ||
fail "a missing migration directory produced output: $missing_dir_output"

# The runner invokes each migration with sh, so the shebang decides nothing.
# A migration that promises bash and then uses a bashism breaks under dash;
# declaring sh also puts it under the dialect pass in test_shell.
shipped_count=0
for migration in "$REPO_ROOT"/Scripts/migrations/*.sh; do
[ -f "$migration" ] || continue
shipped_count=$((shipped_count + 1))

case $(head -n 1 "$migration") in
'#!'*[!a-z]sh | '#!'*[!a-z]sh' '*) ;;
*) fail "${migration#"$REPO_ROOT"/} is run with sh but does not declare it" ;;
esac
done

# Without this the check passes on an empty directory, reporting zero problems
# because it looked at nothing.
[ "$shipped_count" -gt 0 ] ||
fail "no migrations found in Scripts/migrations, the shebang check inspected nothing"

printf ' %d shipped migration(s) checked\n' "$shipped_count"

Comment thread
coderabbitai[bot] marked this conversation as resolved.
finish
Loading