fix(issue-21): reviews 8f141362 N1 and 275fdc4f R4/R5 - clean refusal for a broken worktree, no init over existing files, check retained submodule repositories
- N1 (planner-reviewer-claude-01) worktree_remove.sh: when `git status` fails in the worktree (e.g. its directory was deleted), judge_tree returned before METHOD was set and the summary line died under set -u with exit 1. METHOD now has its default before the function; the refusal is exit 3 with "nothing was changed", plus a `git worktree prune` hint when the directory is gone. Same for --dry-run. - R4 (planner-reviewer-codex-01) worktree_prepare.sh: a submodule path that is not initialised yet but already holds files made the clone fail, and the rollback's `deinit -f` then deleted those files. --submodules now refuses such a path up front (new exit 3, nothing changed), which also makes the rollback's premise true: after a failed attempt, everything under a not-yet-initialised path was created by that attempt. - R5 (planner-reviewer-codex-01) lib_py/worktree.py: `submodule foreach` visits initialised checkouts only, but a deinit'd submodule keeps its repository - stash and unpushed commits included - under the worktree's modules/, which the forced removal deletes. Every repository under modules/ (nested ones included) is now checked directly (`--git-dir`): commits on no remote (HEAD included) and refs/stash with its reflog. A directory that looks like a partial repository cannot be checked and refuses. - Also: the rollback loops are C-style (`seq 0 -1` counts down on BSD), and prepare warns that --base is ignored for an existing branch. Tests W6i (deleted worktree directory, with and without --dry-run), W6j and W6j2 (stash / unpushed commit in a deinit'd submodule's repository) and W6k (existing files in an uninitialised submodule path). Skill docs and the design doc describe the new refusal and the wider repository check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
This commit is contained in:
1 parent
bbbbb95ef6
commit
c7c559b7aa
7 files changed
+168
-8
No files matched your search
@@ -14,7 +14,8 @@
|
||||
# modules/ trace that makes a plain `git worktree remove` refuse) -> optional
|
||||
# creator session registered in the control root's .mam -> summary with BASE=.
|
||||
#
|
||||
# Exit codes: 0 ok, 1 git / submodule / create failure, 2 usage or path rule.
|
||||
# Exit codes: 0 ok, 1 git / submodule / create failure, 2 usage or path rule,
|
||||
# 3 refused to protect existing content (nothing changed).
|
||||
set -euo pipefail
|
||||
|
||||
_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -98,6 +99,9 @@ else
|
||||
fi
|
||||
mkdir -p "$(dirname "$WT")"
|
||||
if git -C "$MAIN" show-ref --verify --quiet "refs/heads/$BRANCH"; then
|
||||
if [ -n "$BASE_REF" ]; then
|
||||
echo "WARNING: branch '$BRANCH' already exists; --base '$BASE_REF' is ignored (the worktree checks the branch out as it is)" >&2
|
||||
fi
|
||||
git -C "$MAIN" worktree add "$WT" "$BRANCH" || exit 1
|
||||
else
|
||||
git -C "$MAIN" worktree add -b "$BRANCH" "$WT" "${BASE_REF:-HEAD}" || exit 1
|
||||
@@ -151,18 +155,34 @@ if [ -f "$WT/.gitmodules" ]; then
|
||||
fi
|
||||
done
|
||||
done < <(git config --file "$WT/.gitmodules" --get-regexp '^submodule\..*\.path$' 2>/dev/null || true)
|
||||
# A submodule path that is not initialised yet but already holds files
|
||||
# (review R4): git refuses to clone into it, and a rollback could not tell
|
||||
# those files from its own - refuse before changing anything. With this,
|
||||
# everything under a not-yet-initialised path after a failed attempt was
|
||||
# created by that attempt, which is what the rollback relies on.
|
||||
blocked=()
|
||||
for ((i = 0; i < ${#sm_names[@]}; i++)); do
|
||||
p="$WT/${sm_paths[$i]}"
|
||||
if [ "${sm_pre[$i]}" != "1" ] && [ -e "$p" ] && [ -n "$(ls -A "$p" 2>/dev/null)" ]; then
|
||||
blocked+=("${sm_paths[$i]}")
|
||||
fi
|
||||
done
|
||||
if [ "${#blocked[@]}" -gt 0 ]; then
|
||||
echo "REFUSED: submodule path(s) not initialised yet but not empty: ${blocked[*]}. Initialising would fail on them and a rollback could not tell their files from its own; move the files away (or drop --submodules). Nothing was changed." >&2
|
||||
exit 3
|
||||
fi
|
||||
if ! git -C "$WT" submodule update --init --recursive; then
|
||||
# A submodule cloned by this attempt leaves a checkout whose .git file
|
||||
# points into modules/<name>: deinit it before its git dir goes, or the
|
||||
# worktree stops being a usable git checkout.
|
||||
for i in $(seq 0 $(( ${#sm_names[@]} - 1 ))); do
|
||||
for ((i = 0; i < ${#sm_names[@]}; i++)); do
|
||||
if [ "${sm_pre[$i]}" = "1" ]; then continue; fi
|
||||
git -C "$WT" submodule deinit -q -f -- "${sm_paths[$i]}" >/dev/null 2>&1 || true
|
||||
if [ -f "$WT/${sm_paths[$i]}/.git" ]; then rm -f "$WT/${sm_paths[$i]}/.git"; fi
|
||||
rm -rf "$gd/modules/${sm_names[$i]}"
|
||||
done
|
||||
if [ "$had_modules" != "1" ]; then rm -rf "$gd/modules"; fi
|
||||
for i in $(seq 0 $(( ${#cfg_keys[@]} - 1 ))); do
|
||||
for ((i = 0; i < ${#cfg_keys[@]}; i++)); do
|
||||
if [ "${cfg_had[$i]}" = "1" ]; then
|
||||
git -C "$MAIN" config "${cfg_keys[$i]}" "${cfg_vals[$i]}"
|
||||
else
|
||||
|
||||
Reference in new issue
Block a user