Skip to content

Commit eb7259b

Browse files
committed
chore: worktree 생성 스크립트 추가
1 parent b94872b commit eb7259b

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ local-test-cov-shop:
9090
--cov-report=term-missing \
9191
--cov-fail-under=100
9292

93+
# Git worktree helpers — per-branch worktree with its own Postgres DB.
94+
# See scripts/dev-worktree.sh.
95+
# make local-worktree-add branch=feat/foo [dir=../backend-foo]
96+
# make local-worktree-remove dir=../backend-foo
97+
local-worktree-add:
98+
@$(if $(branch),,$(error branch=<name> is required))
99+
@./scripts/dev-worktree.sh add "$(branch)" "$(dir)"
100+
101+
local-worktree-remove:
102+
@$(if $(dir),,$(error dir=<worktree-path> is required))
103+
@./scripts/dev-worktree.sh remove "$(dir)"
104+
105+
93106
# Devtools
94107
hooks-install: local-setup
95108
uv run pre-commit install

scripts/dev-worktree.sh

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env bash
2+
# Spin up / tear down a per-branch git worktree with its own Postgres DB
3+
# so multiple branches can run migrations & tests without colliding.
4+
#
5+
# scripts/dev-worktree.sh add <branch> [worktree-dir]
6+
# scripts/dev-worktree.sh remove <worktree-dir>
7+
#
8+
# Reads envfile/.env.local for Postgres credentials; the new worktree
9+
# gets a copy with only DATABASE_NAME rewritten.
10+
set -euo pipefail
11+
12+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
13+
ENV_REL="envfile/.env.local"
14+
SRC_ENV="$REPO_ROOT/$ENV_REL"
15+
16+
usage() {
17+
awk 'NR>1 && /^[^#]/ {exit} NR>1 {sub(/^# ?/, ""); print}' "$0"
18+
exit "${1:-0}"
19+
}
20+
21+
die() { echo "error: $*" >&2; exit 1; }
22+
23+
load_db_env() {
24+
local f="$1"
25+
[ -f "$f" ] || die "env file not found: $f"
26+
unset DB_HOST DB_PORT DB_USER DB_PASSWORD DB_NAME
27+
local key val
28+
while IFS='=' read -r key val; do
29+
case "$key" in
30+
DATABASE_HOST) DB_HOST="$val" ;;
31+
DATABASE_PORT) DB_PORT="$val" ;;
32+
DATABASE_USER) DB_USER="$val" ;;
33+
DATABASE_PASSWORD) DB_PASSWORD="$val" ;;
34+
DATABASE_NAME) DB_NAME="$val" ;;
35+
esac
36+
done < "$f"
37+
local v
38+
for v in DB_HOST DB_PORT DB_USER DB_PASSWORD DB_NAME; do
39+
[ -n "${!v:-}" ] || die "$f: missing DATABASE_${v#DB_}"
40+
done
41+
}
42+
43+
pg() {
44+
PGPASSWORD="$DB_PASSWORD" "$1" -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" "${@:2}"
45+
}
46+
47+
sanitize() {
48+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' \
49+
| sed -E 's/[^a-z0-9]+/_/g; s/^_+|_+$//g'
50+
}
51+
52+
cmd_add() {
53+
local branch="${1:-}"; [ -n "$branch" ] || usage 1
54+
local slug; slug=$(sanitize "$branch")
55+
local wt_dir="${2:-$REPO_ROOT/../backend-$slug}"
56+
57+
load_db_env "$SRC_ENV"
58+
local new_db="${DB_NAME}__${slug}"
59+
[ "${#new_db}" -le 63 ] || die "db name '$new_db' exceeds PG 63-char limit"
60+
[ "$new_db" != "$DB_NAME" ] || die "computed db name collides with source"
61+
62+
echo "→ worktree dir : $wt_dir"
63+
echo "→ branch : $branch"
64+
echo "→ database : $new_db"
65+
66+
if [ -d "$wt_dir" ]; then
67+
echo " worktree dir already exists — skipping git worktree add"
68+
elif git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/$branch"; then
69+
git -C "$REPO_ROOT" worktree add "$wt_dir" "$branch"
70+
else
71+
git -C "$REPO_ROOT" worktree add -b "$branch" "$wt_dir"
72+
fi
73+
74+
local new_env="$wt_dir/$ENV_REL"
75+
mkdir -p "$(dirname "$new_env")"
76+
awk -v line="DATABASE_NAME=$new_db" '
77+
/^DATABASE_NAME=/ { print line; seen=1; next }
78+
{ print }
79+
END { if (!seen) print line }
80+
' "$SRC_ENV" > "$new_env"
81+
82+
# pytest-django will derive test_<name> on first run
83+
local err
84+
if err=$(pg createdb "$new_db" 2>&1); then
85+
echo " created db $new_db"
86+
elif printf '%s' "$err" | grep -q "already exists"; then
87+
echo " db $new_db already exists — skipping createdb"
88+
else
89+
printf '%s\n' "$err" >&2
90+
exit 1
91+
fi
92+
93+
cat <<EOF
94+
95+
next:
96+
cd "$wt_dir"
97+
uv sync
98+
uv run python app/manage.py migrate
99+
uv run pytest # will auto-create test_$new_db
100+
EOF
101+
}
102+
103+
cmd_remove() {
104+
local wt_dir="${1:-}"; [ -n "$wt_dir" ] || usage 1
105+
[ -d "$wt_dir" ] || die "not a directory: $wt_dir"
106+
local wt_env="$wt_dir/$ENV_REL"
107+
108+
load_db_env "$SRC_ENV"
109+
local src_host="$DB_HOST" src_port="$DB_PORT" src_user="$DB_USER" src_db="$DB_NAME"
110+
load_db_env "$wt_env"
111+
[ "$DB_HOST:$DB_PORT:$DB_USER" = "$src_host:$src_port:$src_user" ] \
112+
|| die "worktree Postgres ($DB_HOST:$DB_PORT/$DB_USER) differs from source; refusing"
113+
[ "$DB_NAME" != "$src_db" ] || die "worktree db equals source db ($src_db); refusing"
114+
115+
echo "→ drop db : $DB_NAME (+ test_$DB_NAME)"
116+
echo "→ remove wt : $wt_dir"
117+
read -r -p "proceed? [y/N] " ans
118+
[ "$ans" = "y" ] || [ "$ans" = "Y" ] || { echo "aborted"; exit 0; }
119+
120+
pg dropdb --if-exists "$DB_NAME"
121+
pg dropdb --if-exists "test_$DB_NAME"
122+
git -C "$REPO_ROOT" worktree remove "$wt_dir"
123+
}
124+
125+
main() {
126+
local sub="${1:-}"; shift || true
127+
case "$sub" in
128+
add) cmd_add "$@" ;;
129+
remove|rm) cmd_remove "$@" ;;
130+
-h|--help|"") usage 0 ;;
131+
*) usage 1 ;;
132+
esac
133+
}
134+
135+
main "$@"

0 commit comments

Comments
 (0)