mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 18:50:18 +01:00
Stop gstack-uninstall deleting nested worktrees, locked worktrees and a .gstack repo (GST-2268) (#4267)
* Stop gstack-uninstall deleting nested worktrees, locked worktrees and a .gstack repo (GST-2268) The worktree check now walks every checkout under .gstack-worktrees/, nested ones included, and keeps a submodule of a clean lane covered by that lane's status. A locked worktree stops the uninstall even with --force-worktrees. A .gstack/ that holds its own git repository is kept with a message. The check runs again right before .gstack-worktrees/ is deleted, so work saved while the prompt waited is kept (exit 2). The manual steps in docs/uninstall.md no longer rm -rf the per-project folders. test/uninstall.test.ts is already excluded from the Windows subset by scripts/lib/windows-curation.ts (it spawns a bin/ shebang script). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Keep a lane's submodule when it holds commits no remote-tracking branch has (GST-2268) The submodule exemption trusted the lane's status, which only compares the submodule's HEAD to the recorded gitlink. Commits on the submodule's own branches or stash live in the lane's git dir and were deleted with exit 0. Uninstall now refuses such a submodule, and matches the gitlink by exact path instead of a prefix or glob pathspec. Review-Finding: s11/outside-voice#1 reader=outside-voice Review-Finding: s11/adversarial-native#1 reader=adversarial-native Review-Finding: p1/specialist-batched#1 reader=testing Review-Finding: p1/red-team#3 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Explain the submodule rule in the uninstall docs (GST-2268) docs/uninstall.md now says a submodule inside a clean lane goes only when its commits are on a remote-tracking branch, and the changelog entry drops a mid-sentence colon. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Make every gstack-uninstall keep check run before anything is deleted (GST-2268) The post-prompt worktree check now runs right after confirmation, before the install root or anything else is removed, so running uninstall from inside the gstack install no longer deletes a worktree edited during the prompt. A .gstack/ holding its own git repository inside the install being removed now stops the uninstall. A failing git worktree list stops it too, even with --force-worktrees. The manual cleanup in docs/uninstall.md now matches worktree paths by prefix, so it never removes a worktree outside the project whose path merely contains it. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Also stop gstack-uninstall when a .gstack repo sits inside a Cursor, OpenCode, Copilot, Antigravity or Gemini install (GST-2268) Review-Finding: p1/specialist-batched#1 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Split a run-on sentence in the uninstall release note (GST-2268) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
4685fa9233
commit
6c23638d6d
4 files changed
+417
-33
No files matched your search
+25
-3
@@ -10,7 +10,7 @@ If gstack is installed on your machine:
|
||||
|
||||
This handles skills, symlinks, harness registrations (the files `gstack-harness` wrote into OpenClaw, Hermes, a Dot or another harness, from their ledgers, plus the opt-in [start hook](hosts.md#start-hook), which it turns off through the harness's own CLI), global state (`~/.gstack/`), project-local state, browse daemons, and temp files. Use `--keep-state` to preserve config and analytics; it also keeps the repo's `.gstack/` and `.gstack-worktrees/`. Use `--force` to skip confirmation.
|
||||
|
||||
A plain uninstall keeps the git-tracked files under `.gstack/` (such as ship policy) and removes only the ones git ignores. It stops with exit 2, before removing anything, when a checkout under `.gstack-worktrees/` has uncommitted changes, has commits no branch or tag holds, or is not a worktree this repo registered. Pass `--force-worktrees` to delete those checkouts anyway. When `.gstack-worktrees` is a symlink, uninstall removes only the link and never follows it, so the folder it points at and the worktrees there are kept. It checks only the top-level checkouts: a worktree created inside another checkout under `.gstack-worktrees/` is not checked and is deleted with its parent, so commit or stash its work before you uninstall.
|
||||
A plain uninstall keeps the git-tracked files under `.gstack/` (such as ship policy) and removes only the ones git ignores. It stops with exit 2, before removing anything, when a checkout under `.gstack-worktrees/` has uncommitted changes, has commits no branch or tag holds, or is not a worktree this repo registered. Pass `--force-worktrees` to delete those checkouts anyway. Every checkout is checked, including a worktree created inside another checkout under `.gstack-worktrees/`. A git submodule inside a clean checkout is deleted with it only when every commit it holds is on a remote-tracking branch; otherwise it stops the uninstall. A locked worktree (`git worktree lock`) always stops the uninstall, even with `--force-worktrees`; unlock it with `git worktree unlock <path>` if you want it gone. If `git worktree list` fails, the uninstall stops too, even with `--force-worktrees`. The check runs again after you confirm, before anything is removed, so a checkout you edit while the confirmation prompt waits is kept: uninstall exits 2 and removes nothing. Run from inside the gstack install itself, a `.gstack/` that holds its own git repository also stops the uninstall, because deleting the install would take it along. A `.gstack/` that is its own git repository is kept, with a message saying so, because its commits may exist nowhere else. When `.gstack-worktrees` is a symlink, uninstall removes only the link and never follows it, so the folder it points at and the worktrees there are kept.
|
||||
|
||||
## Option 2: Manual removal (no local repo)
|
||||
|
||||
@@ -62,10 +62,32 @@ rm -rf ~/.gemini/skills/gstack* 2>/dev/null
|
||||
rm -f /tmp/gstack-* 2>/dev/null
|
||||
|
||||
# 7. Per-project cleanup (run from each project root)
|
||||
rm -rf .gstack .gstack-worktrees .claude/skills/gstack 2>/dev/null
|
||||
rm -rf .agents/skills/gstack* .factory/skills/gstack* 2>/dev/null
|
||||
rm -rf .claude/skills/gstack .agents/skills/gstack* .factory/skills/gstack* 2>/dev/null
|
||||
# Browse state: removes only the files git ignores, so tracked files such as
|
||||
# ship policy, and a .gstack/ that is its own git repo, stay.
|
||||
git clean -fdXq -- .gstack 2>/dev/null
|
||||
# Worktrees, deepest first: each goes only when it has no uncommitted changes,
|
||||
# no commits a branch or tag lacks, and no checkout inside it. git refuses a
|
||||
# locked one. Anything printed as kept still holds work: save it, then re-run.
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
git worktree list --porcelain | sed -n 's/^worktree //p' | sort -r |
|
||||
while IFS= read -r wt; do
|
||||
case "$wt" in "$root/.gstack-worktrees/"*) ;; *) continue ;; esac
|
||||
if [ -z "$(git -C "$wt" status --porcelain --untracked-files=all)" ] &&
|
||||
[ -z "$(git -C "$wt" log --oneline -1 HEAD --not --branches --tags)" ] &&
|
||||
[ -z "$(find "$wt" -mindepth 2 -name .git -print -quit)" ]; then
|
||||
git worktree remove "$wt" || echo "kept $wt"
|
||||
else
|
||||
echo "kept $wt: it holds work or another checkout"
|
||||
fi
|
||||
done
|
||||
find .gstack-worktrees -depth -type d -empty -delete 2>/dev/null || true
|
||||
```
|
||||
|
||||
Never `rm -rf .gstack-worktrees` by hand: a checkout there can hold uncommitted
|
||||
work, commits no branch has, or another worktree nested inside it, and `rm -rf`
|
||||
deletes all of it. Anything the loop above keeps, check and remove yourself.
|
||||
|
||||
Manual removal leaves gstack's hook entries behind in `~/.claude/settings.json`
|
||||
(the uninstall script removes all of them for you, including entries whose
|
||||
`_gstack_source` tag was stripped). Edit that file and delete every hook whose
|
||||
|
||||
Reference in new issue
Block a user