script: migrate workdir layout

This commit is contained in:
Keisuke Hirata 2026-07-24 19:01:09 +09:00
parent 1d64618b4a
commit 7caf6cd6e6
No known key found for this signature in database

View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
scripts/migrate-workdir-layout-20260724.sh --workdir-target <PATH> [--apply]
Migrates old runtime workdir materialization layout:
<workdir-target>/working-directories/<id>/materialization.json
<workdir-target>/working-directories/<id>/root/<repository-id>/
to the new layout:
<workdir-target>/<id>/materialization.json
<workdir-target>/<id>/checkout/
The script defaults to dry-run. It mutates files only when --apply is passed.
Run this while the runtime/server and workers using the target are stopped.
Options:
--workdir-target <PATH> Runtime workdir materialization target.
--apply Perform migration. Without this, only prints actions.
-h, --help Show this help.
Requirements:
bash, git, jq, mktemp
USAGE
}
log() {
printf '%s\n' "$*"
}
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}
json_string() {
local file=$1
local expr=$2
jq -er "$expr | strings" "$file"
}
apply=0
workdir_target=""
while [[ $# -gt 0 ]]; do
case "$1" in
--workdir-target)
[[ $# -ge 2 ]] || fail "--workdir-target requires a path"
workdir_target=$2
shift 2
;;
--apply)
apply=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
fail "unknown argument: $1"
;;
esac
done
[[ -n "$workdir_target" ]] || fail "--workdir-target is required"
need_cmd git
need_cmd jq
need_cmd mktemp
[[ -d "$workdir_target" ]] || fail "workdir target does not exist or is not a directory: $workdir_target"
old_root=$workdir_target/working-directories
if [[ ! -d "$old_root" ]]; then
log "no old workdir layout found: $old_root"
exit 0
fi
if [[ $apply -eq 0 ]]; then
log "dry-run: no files will be changed; pass --apply to migrate"
else
log "apply: migrating old workdir layout under $workdir_target"
fi
shopt -s nullglob
records=("$old_root"/*/materialization.json)
shopt -u nullglob
if [[ ${#records[@]} -eq 0 ]]; then
log "no materialization records found under $old_root"
exit 0
fi
migrated=0
for record in "${records[@]}"; do
old_dir=$(dirname "$record")
workdir_id=$(basename "$old_dir")
new_dir=$workdir_target/$workdir_id
new_record=$new_dir/materialization.json
new_checkout=$new_dir/checkout
old_checkout=$(json_string "$record" '.root')
source_repository_path=$(json_string "$record" '.source_repository_path')
[[ "$old_checkout" = "$old_dir"/root/* ]] || fail "$record root is not under expected old root/<repository-id> layout: $old_checkout"
[[ -d "$old_checkout" ]] || fail "$record root does not exist or is not a directory: $old_checkout"
[[ -d "$source_repository_path" ]] || fail "$record source_repository_path does not exist or is not a directory: $source_repository_path"
root_parent=$(dirname "$old_checkout")
[[ "$root_parent" = "$old_dir/root" ]] || fail "$record root parent is not $old_dir/root: $root_parent"
shopt -s nullglob dotglob
root_children=("$old_dir/root"/*)
shopt -u nullglob dotglob
[[ ${#root_children[@]} -eq 1 ]] || fail "$old_dir/root must contain exactly one checkout; found ${#root_children[@]}"
[[ "${root_children[0]}" = "$old_checkout" ]] || fail "$record root does not match the only checkout under $old_dir/root"
if [[ -e "$new_dir" && "$new_dir" != "$old_dir" ]]; then
fail "target workdir directory already exists: $new_dir"
fi
if [[ -e "$new_checkout" ]]; then
fail "target checkout already exists: $new_checkout"
fi
if [[ -e "$new_record" ]]; then
fail "target materialization record already exists: $new_record"
fi
log "workdir $workdir_id"
log " move checkout: $old_checkout -> $new_checkout"
log " rewrite record: $record -> $new_record"
log " repair git worktree metadata in: $source_repository_path"
if [[ $apply -eq 1 ]]; then
mkdir -p "$new_dir"
mv "$old_checkout" "$new_checkout"
tmp_record=$(mktemp "$new_dir/materialization.json.tmp.XXXXXX")
jq --arg root "$new_checkout" '.root = $root' "$record" > "$tmp_record"
mv "$tmp_record" "$new_record"
git -C "$source_repository_path" worktree repair "$new_checkout"
rm "$record"
rmdir "$old_dir/root"
rmdir "$old_dir"
fi
migrated=$((migrated + 1))
done
if [[ $apply -eq 1 ]]; then
rmdir "$old_root" 2>/dev/null || true
log "migrated $migrated workdir(s)"
else
log "dry-run complete: $migrated workdir(s) would be migrated"
fi