fix(issue-21): review ffb5055e R1-R3 - re-check the tree after --stop, refuse submodule stashes, roll a partial submodule init back to a usable checkout
Review ffb5055e (planner-reviewer-codex-01): - R1 [P1] worktree_remove.sh: the clean-tree and submodule checks ran only before --stop, so work a stopping session wrote (or committed, or stashed) was deleted by the forced removal. The checks are one function now, run before any change and again right before the removal; any finding then refuses with exit 3 (sessions already stopped, worktree kept), and the removal method follows that latest look. - R2 [P1] lib_py/worktree.py: a submodule stash is reachable from no branch and leaves the status clean, and the forced removal deletes the worktree's own submodule repositories. `git stash list` in every submodule is a separate finding (`stashes`), refused like dirty/unpushed; errors fail closed as before. - R3 [P2] worktree_prepare.sh: when one submodule cloned and a later one failed, the rollback deleted modules/ while the cloned checkout's .git file still pointed into it, and `git status` failed in the worktree. The rollback now undoes only this attempt: each submodule whose git dir did not exist before is deinitialised before its git dir is deleted; submodules initialised earlier are kept; shared submodule.* config values are restored exactly (new keys removed, pre-existing values put back - init and deinit both write .git/config). A final `git status` either confirms a usable checkout or prints what to inspect. Tests W6e (a write during stop keeps the worktree and the file), W6f (a submodule stash refuses), W6g (partial failure on a fresh worktree: usable, no modules/, no new config, then prepare and a plain removal work) and W6h (a failing retry keeps the earlier `sm` init and its config value). The skill docs and the design doc describe the recheck, the stash rule and the scoped rollback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
This commit is contained in:
1 parent
7988db9adf
commit
bbbbb95ef6
7 files changed
+181
-42
No files matched your search
@@ -130,21 +130,51 @@ if [ -f "$WT/.gitmodules" ]; then
|
||||
if [ "$SUBMODULES" = "1" ]; then
|
||||
gd="$(git -C "$WT" rev-parse --absolute-git-dir)"
|
||||
had_modules=0
|
||||
[ -e "$gd/modules" ] && had_modules=1
|
||||
new_keys=()
|
||||
while read -r key _; do
|
||||
if [ -e "$gd/modules" ]; then had_modules=1; fi
|
||||
# The state before this attempt, so a failure undoes only what it created
|
||||
# (review R3): per submodule its path and whether its git dir already
|
||||
# existed (an earlier init - kept), and the exact shared config values
|
||||
# (init and deinit both write the common .git/config). Indexed arrays only
|
||||
# (bash 3.2 has no associative ones).
|
||||
sm_names=(); sm_paths=(); sm_pre=(); cfg_keys=(); cfg_had=(); cfg_vals=()
|
||||
while read -r key path; do
|
||||
[ -n "$key" ] || continue
|
||||
name="${key#submodule.}"; name="${name%.path}"
|
||||
sm_names+=("$name"); sm_paths+=("$path")
|
||||
if [ -e "$gd/modules/$name" ]; then sm_pre+=(1); else sm_pre+=(0); fi
|
||||
for k in url active; do
|
||||
git -C "$MAIN" config --get "submodule.$name.$k" >/dev/null 2>&1 || new_keys+=("submodule.$name.$k")
|
||||
cfg_keys+=("submodule.$name.$k")
|
||||
if v="$(git -C "$MAIN" config --get "submodule.$name.$k" 2>/dev/null)"; then
|
||||
cfg_had+=(1); cfg_vals+=("$v")
|
||||
else
|
||||
cfg_had+=(0); cfg_vals+=("")
|
||||
fi
|
||||
done
|
||||
done < <(git config --file "$WT/.gitmodules" --get-regexp '^submodule\..*\.path$' 2>/dev/null || true)
|
||||
if ! git -C "$WT" submodule update --init --recursive; then
|
||||
[ "$had_modules" = "1" ] || rm -rf "$gd/modules"
|
||||
for k in ${new_keys[@]+"${new_keys[@]}"}; do
|
||||
git -C "$MAIN" config --unset-all "$k" 2>/dev/null || true
|
||||
# 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
|
||||
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
|
||||
echo "ERROR: submodule initialisation failed; its traces (modules/, new submodule.* config keys) were removed so the worktree can still be removed without --force. Re-run without --submodules to finish preparing." >&2
|
||||
if [ "$had_modules" != "1" ]; then rm -rf "$gd/modules"; fi
|
||||
for i in $(seq 0 $(( ${#cfg_keys[@]} - 1 ))); do
|
||||
if [ "${cfg_had[$i]}" = "1" ]; then
|
||||
git -C "$MAIN" config "${cfg_keys[$i]}" "${cfg_vals[$i]}"
|
||||
else
|
||||
git -C "$MAIN" config --unset-all "${cfg_keys[$i]}" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
if st_err="$(git -C "$WT" status --porcelain 2>&1 >/dev/null)"; then
|
||||
echo "ERROR: submodule initialisation failed; this attempt's traces (new submodule checkouts, their git dirs, new submodule.* config keys) were removed and earlier submodule state was kept, so the worktree stays usable and removable. Re-run without --submodules to finish preparing." >&2
|
||||
else
|
||||
echo "ERROR: submodule initialisation failed, and '$WT' is not a usable git checkout after the rollback: $st_err" >&2
|
||||
echo " inspect it with: git -C '$WT' submodule status - do not force-remove it before checking its submodules" >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
SUB_STATE="initialised"
|
||||
|
||||
Reference in new issue
Block a user