diff --git a/bin/omarchy-dev-add-migration b/bin/omarchy-dev-add-migration index bed8af3ffd2..87de6e0bf92 100755 --- a/bin/omarchy-dev-add-migration +++ b/bin/omarchy-dev-add-migration @@ -31,8 +31,19 @@ done cd "${OMARCHY_PATH:-$(pwd)}" mkdir -p migrations -migration_file="migrations/$(git log -1 --format=%cd --date=unix).sh" -touch "$migration_file" + +# Use wall-clock time, not HEAD's committer date, so two branches cut from the +# same commit do not mint the same migration id (#13516). +id=$(date +%s) +migration_file="migrations/${id}.sh" +# noclobber creates the file with O_EXCL, so concurrent runs cannot claim one id. +set -o noclobber +until { : >"$migration_file"; } 2>/dev/null; do + [[ -e $migration_file ]] || { echo "Cannot create $PWD/$migration_file" >&2; exit 1; } + id=$((id + 1)) + migration_file="migrations/${id}.sh" +done +set +o noclobber if (( ! no_edit )); then nvim "$migration_file" diff --git a/test/shell.d/dev-add-migration-race-test.sh b/test/shell.d/dev-add-migration-race-test.sh new file mode 100755 index 00000000000..46b1bffb902 --- /dev/null +++ b/test/shell.d/dev-add-migration-race-test.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +# Pin the clock so both runs start from the same second. +stub_bin="$test_tmp/bin" +mkdir -p "$stub_bin" +cat >"$stub_bin/date" <<'SH' +#!/bin/bash +[[ ${1:-} == "+%s" ]] || exit 1 +echo 1790700000 +SH +chmod +x "$stub_bin/date" + +for trial in $(seq 1 20); do + checkout="$test_tmp/omarchy-$trial" + mkdir -p "$checkout/migrations" + + PATH="$stub_bin:$PATH" OMARCHY_PATH="$checkout" "$ROOT/bin/omarchy-dev-add-migration" --no-edit >"$test_tmp/$trial.a" & + first=$! + PATH="$stub_bin:$PATH" OMARCHY_PATH="$checkout" "$ROOT/bin/omarchy-dev-add-migration" --no-edit >"$test_tmp/$trial.b" & + second=$! + wait "$first" "$second" + + [[ $(<"$test_tmp/$trial.a") != "$(<"$test_tmp/$trial.b")" ]] || + fail "two concurrent runs in one checkout create distinct migrations" "trial $trial: both printed $(<"$test_tmp/$trial.a")" +done +pass "two concurrent runs in one checkout create distinct migrations" diff --git a/test/shell.d/dev-add-migration-test.sh b/test/shell.d/dev-add-migration-test.sh new file mode 100755 index 00000000000..3859560bb68 --- /dev/null +++ b/test/shell.d/dev-add-migration-test.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +# A checkout whose HEAD was committed at 1789095456, the id #13516 saw reused. +checkout="$test_tmp/omarchy" +mkdir -p "$checkout/migrations" +git -C "$checkout" init -q +GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.invalid \ +GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.invalid \ +GIT_AUTHOR_DATE=@1789095456 GIT_COMMITTER_DATE=@1789095456 \ + git -C "$checkout" commit --allow-empty -q -m base + +# Pin the clock so the expected ids are exact. +stub_bin="$test_tmp/bin" +mkdir -p "$stub_bin" +cat >"$stub_bin/date" <<'SH' +#!/bin/bash +[[ ${1:-} == "+%s" ]] || exit 1 +echo 1790700000 +SH +chmod +x "$stub_bin/date" + +add_migration() { + PATH="$stub_bin:$PATH" OMARCHY_PATH="$checkout" "$ROOT/bin/omarchy-dev-add-migration" --no-edit +} + +created=$(add_migration) +[[ $created == "$checkout/migrations/1790700000.sh" && -f $created ]] || + fail "a new migration is named after the current time, not HEAD's commit time" "created: $created" +pass "a new migration is named after the current time, not HEAD's commit time" + +created=$(add_migration) +[[ $created == "$checkout/migrations/1790700001.sh" && -f $created ]] || + fail "a second migration in the same second takes the next free id" "created: $created" +pass "a second migration in the same second takes the next free id"