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
2 changes: 1 addition & 1 deletion .delphi/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# MINOR something new that existing clones need nothing for
# MAJOR clones need cleanup (MINOR while 0.x): add .delphi/migrations/<version>-<slug>.sh, which
# setup.sh runs once per workspace in each clone (rules in Delphi's CLAUDE.md)
version: 1.2.0
version: 2.0.0
files: # repo-relative paths; a trailing / means everything under it
- .delphi/manifest.yml
- .delphi/.gitattributes
Expand Down
27 changes: 27 additions & 0 deletions .delphi/migrations/2.0.0-bare-repos-to-worktree-store.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# 2.0.0: a bare repos/<name> (the store before 1.1.0) becomes the worktree store, worktrees/<name>/.bare,
# and repos/<name> is vacated for setup.sh to clone normally. Moves it (git worktree repair, so its
# worktrees follow) and sets origin/HEAD. Skips, reporting why, one with no origin, one whose branches or
# worktree HEADs aren't on origin, or whose .bare exists, or that can't be fetched to check.
set -euo pipefail
for d in repos/*; do
[ -d "$d" ] || continue
[ "$(git -C "$d" rev-parse --is-bare-repository 2>/dev/null)" = true ] || continue
name=${d#repos/}
to=worktrees/$name/.bare
skip() { echo "migrate: $d $1; left it (move it to $to by hand)" >&2; }
git -C "$d" remote get-url origin >/dev/null 2>&1 || { skip "has no origin remote"; continue; }
[ ! -e "$to" ] || { skip "and $to both exist"; continue; }
git -C "$d" fetch -q origin '+refs/heads/*:refs/remotes/origin/*' </dev/null 2>/dev/null || { skip "could not be fetched from origin"; continue; }
heads=$({ git -C "$d" for-each-ref --format='%(objectname)' refs/heads
git -C "$d" worktree list --porcelain | awk '/^HEAD / { print $2 }'; } | grep -v '^0*$' | sort -u)
# shellcheck disable=SC2086 # object names
if [ -n "$heads" ] && [ -n "$(git -C "$d" rev-list $heads --not --remotes=origin | head -n 1)" ]; then
skip "has branches or worktree commits not on origin"; continue
fi
mkdir -p "worktrees/$name"
mv "$d" "$to"
git -C "$to" worktree repair "$PWD"/worktrees/"$name"/* 2>/dev/null || true
git -C "$to" remote set-head origin -a >/dev/null </dev/null || true
echo "migrate: moved $d to $to"
done
1 change: 1 addition & 0 deletions .delphi/versions
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
1.0.0 44693a895995 1.0: workspaces: clones live at workspaces/<name> (was linked/<name>; DELPHI_WORKSPACES, was DELPHI_LINKED); setup.sh also runs each one's own setup.sh, and a workspace inside another's workspaces/ skips its own workspaces: (migration)
1.1.0 a3446f7f763c setup.sh clones each repos: entry at repos/<name> (a normal clone on its default branch, was a bare store) and adds no worktree; new-worktree.sh makes worktrees from that clone; older bare stores are left as they are
1.2.0 d139aaf9f4a5 worktrees come from their own bare store, worktrees/<name>/.bare, separate from the plain clone at repos/<name>; setup.sh makes the store and the default-branch worktree again; an older bare store at repos/<name> is left as it is
2.0.0 750acf945979 a bare repos/<name> from before 1.1.0 becomes the worktree store, worktrees/<name>/.bare, with its worktrees, and repos/<name> is cloned normally (migration; skipped with a message if it has no origin or work not on origin)
27 changes: 21 additions & 6 deletions tests/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ for r in lib1 lib2; do
[ "$(git -C "$t/ws/repos/$r" branch --show-current)" = "$def" ] || fail "repos/$r not on $def"
[ "$(git -C "$t/ws/worktrees/$r/$def" branch --show-current)" = "$def" ] || fail "$r default worktree not on $def"
[ "$(git -C "$t/ws/worktrees/$r/.bare" rev-parse --is-bare-repository)" = true ] || fail "worktrees/$r/.bare not a bare store"
[ "$(git -C "$t/ws/repos/$r" worktree list | wc -l)" = 1 ] || fail "repos/$r has worktrees"
[ "$(git -C "$t/ws/repos/$r" worktree list | wc -l | tr -d " ")" = 1 ] || fail "repos/$r has worktrees"
done
[ "$(git -C "$t/ws/repos/lib1" rev-parse --is-bare-repository)" = false ] || fail "repos/lib1 is a bare store"
grep -q "repos/lib1: already present" "$t/out" || fail "re-run did not skip"
Expand All @@ -361,10 +361,10 @@ w=$t/legacy && git clone -q -b ws/$AN "$t/origin.git" "$w" && cp "$t/ws/workspac
git init -q --bare "$w/repos/lib2" && git -C "$w/repos/lib2" remote add origin "$t/lib2.git" && git -C "$w/repos/lib2" fetch -q origin &&
git -C "$w/repos/lib2" worktree add -q -b "$def" "$w/worktrees/lib2/$def" "origin/$def" && echo wip >"$w/worktrees/lib2/$def/wip"
/bin/bash "$w/.delphi/setup.sh" >"$t/out" 2>&1 || fail "setup.sh failed on older clones: $(cat "$t/out")"
{ [ "$(git -C "$w/repos/lib2" rev-parse --is-bare-repository)" = true ] && [ "$(git -C "$w/worktrees/lib2/$def" status --porcelain)" = "?? wip" ] &&
[ -d "$w/worktrees/lib2/.bare" ] && [ "$(git -C "$w/repos/lib1" branch --show-current)" = "$def" ] && [ -f "$w/repos/lib1/wip" ] &&
[ -d "$w/worktrees/lib1/$def" ]; } || fail "an older bare store or plain clone in repos/ was not left as it is: $(cat "$t/out")"
ok "setup.sh leaves an older bare store or plain clone in repos/ (and its worktrees) as it is, and adds the store in worktrees/"
{ [ "$(git -C "$w/repos/lib2" rev-parse --is-bare-repository)" = false ] && [ "$(git -C "$w/worktrees/lib2/$def" status --porcelain)" = "?? wip" ] &&
[ "$(git -C "$w/worktrees/lib2/.bare" rev-parse --is-bare-repository)" = true ] && [ "$(git -C "$w/repos/lib1" branch --show-current)" = "$def" ] && [ -f "$w/repos/lib1/wip" ] &&
[ -d "$w/worktrees/lib1/$def" ]; } || fail "an older plain clone in repos/ was not left as it is, or an older bare store was not moved (2.0.0): $(cat "$t/out")"
ok "setup.sh leaves an older plain clone in repos/ as it is; 2.0.0 moves an older bare store there to worktrees/ (worktrees intact)"
git clone -q -c core.autocrlf=true -b ws/$AN "$t/origin.git" "$t/ws-crlf"
cp "$t/ws/workspace.yml" "$t/ws-crlf/"
/bin/bash "$t/ws-crlf/.delphi/setup.sh" >/dev/null 2>"$t/err" || fail "setup.sh fails with core.autocrlf=true"
Expand Down Expand Up @@ -527,7 +527,7 @@ nw code 2-dev origin/develop >/dev/null && [ "$(git -C "$w/worktrees/code/2-dev"
fail "<base> ignored"
[ "$(nw code feat)" = "$w/worktrees/code/feat" ] || fail "rerun did not reuse"
[ "$(git -C "$w/worktrees/code/$def" branch --show-current)" = "$def" ] || fail "default worktree left its branch"
{ [ "$(git -C "$w/repos/code" branch --show-current)" = "$def" ] && [ "$(git -C "$w/repos/code" worktree list | wc -l)" = 1 ] &&
{ [ "$(git -C "$w/repos/code" branch --show-current)" = "$def" ] && [ "$(git -C "$w/repos/code" worktree list | wc -l | tr -d " ")" = 1 ] &&
! git -C "$w/repos/code" rev-parse -q --verify refs/heads/1-new >/dev/null; } || fail "new-worktree touched the clone at repos/code"
ok "new-worktree.sh makes new branches from the default or <base>, checks out existing ones, reuses on rerun, never touching repos/<repo>"
# skill scripts read the workspace from $DELPHI_ROOT (the session hook evals paths.sh's export line) and every folder from paths.sh
Expand Down Expand Up @@ -650,6 +650,21 @@ rm -rf "$w/workspaces" && old_copy "$w/linked/main" main && old_copy "$w/linked/
fail "0.9.0 did not turn old read-only copies into clones on their branch, keeping local changes: $(cat "$t/out")"
git -C "$w/workspaces/$AN" checkout -q CLAUDE.md
ok "0.9.0 turns old shallow, detached, push-disabled copies in linked/ into full clones on their branch, keeping what's in them; 1.0.0 moves them to workspaces/"
git -C "$w" config delphi.$BN.version 1.2.0
rm -rf "$w/repos/lib1" "$w/worktrees/lib1" && git clone -q --bare "$t/lib1.git" "$w/repos/lib1" && git -C "$w/repos/lib1" worktree add -q "$w/worktrees/lib1/old" -b old 2>/dev/null
git -C "$w/repos/lib1" worktree add -q --detach "$w/worktrees/lib1/det" && git -C "$w/worktrees/lib1/det" commit -q --allow-empty -m "unpushed"
{ /bin/bash "$w/.delphi/setup.sh" >/dev/null 2>"$t/err" && grep -q "repos/lib1 has branches or worktree commits not on origin" "$t/err" &&
[ "$(git -C "$w/repos/lib1" rev-parse --is-bare-repository)" = true ]; } || fail "2.0.0 moved a bare repos/lib1 with unpushed commits: $(cat "$t/err")"
git -C "$w/repos/lib1" worktree remove --force "$w/worktrees/lib1/det"
git -C "$w" config delphi.$BN.version 1.2.0
/bin/bash "$w/.delphi/setup.sh" >"$t/out" 2>&1 || fail "2.0.0 failed on a bare repos/lib1: $(cat "$t/out")"
{ grep -q "moved repos/lib1 to worktrees/lib1/.bare" "$t/out" && [ "$(git -C "$w/worktrees/lib1/.bare" rev-parse --is-bare-repository)" = true ] &&
[ ! -e "$w/repos/lib1" ] && git -C "$w/worktrees/lib1/old" status >/dev/null &&
git -C "$w/worktrees/lib1/.bare" worktree list | grep -q "worktrees/lib1/old" &&
git -C "$w/worktrees/lib1/.bare" symbolic-ref -q refs/remotes/origin/HEAD >/dev/null; } || fail "2.0.0 did not move a bare repos/lib1 to .bare, keeping its worktrees: $(cat "$t/out")"
git -C "$w" config delphi.$BN.version 1.2.0
{ /bin/bash "$w/.delphi/setup.sh" >"$t/out" 2>&1 && ! grep -q "migrate:" "$t/out"; } || fail "2.0.0 did something on a second run: $(cat "$t/out")"
ok "2.0.0 moves a bare repos/<name> to worktrees/<name>/.bare with its worktrees, vacating repos/<name> for setup's clone; skips one with unpushed work"
echo "# local" >>"$w/.delphi/switch.sh"
{ /bin/bash "$w/.delphi/setup.sh" >/dev/null 2>"$t/err" && grep -q "not Delphi $VN as released" "$t/err"; } || fail "setup.sh did not warn about an edited .delphi/"
git -C "$w" checkout -q .delphi/switch.sh
Expand Down
Loading