Remove worktrees with initialized submodules by passing --force - #226
Merged
Conversation
git refuses to remove a worktree whose submodules are initialized, even when the tree is clean — the error users saw: ``working trees containing submodules cannot be moved or removed``. Hygiene never passed --force on a clean row, so those worktrees were stuck. The facts now carry hasSubmodules (initialized checkouts only — uninitialized gitlinks were never the problem), read as a non-`-` line of `git submodule status`, gated on .gitmodules locally so repositories without submodules pay zero extra git calls; remotely it is one more round trip. Forcing for submodules is bookkeeping, not discard: a pristine checkout is restorable from upstream, so it does not raise the confirmation. Uncommitted work inside a submodule keeps its protection — the superproject's status already reports it dirty — and the three removal routes (sweeper, resolve moment, card Reclaim) all pass the flag; the card action carries the offer's fact because the offer is cleared in the same action.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
git worktree removerefuses worktrees whose submodules are initialized —fatal: working trees containing submodules cannot be moved or removed(reproduced on git 2.50.1; this repository's ownThirdParty/ghostty+zmxin a loop's worktree is the real-world case). Hygiene's removal passed--forceonly for dirty trees — and a submodule worktree reads clean (status --porcelainis empty) — so those worktrees were stuck: the sweeper offered them, git refused, the sheet named the refusal, nothing could go through.Evidence
--force→ removed, gitdir bookkeeping clean.gitmodulespresent, submodule not initializedM <sub>— already dirty by graphcode's read--force→ removedSo: the refusal is about initialized submodules only, one
--forcealways suffices, and no deinit /rm -rf/ double-force is ever needed.Fix
WorktreeGitFacts.hasSubmodules: initialized checkouts only. Local detection gates on.gitmodules(zero extra git calls for submodule-free repos) then readsgit submodule status(a non--line means populated); remote is one extra SSH round trip.--forcewhen submodules are present — without the discard confirmation: a pristine checkout is restorable from upstream, so the force is bookkeeping, not discard. Uncommitted work inside submodules keeps its protection via the superproject's dirty read (row 3 above).discardsFilesvsneedsForcesplit), resolve moment (auto-remove), and card Reclaim — whose action now carrieshasSubmodules, because the project scope clears the offer in the same action and the fact must survive it.Tests
WorktreeRemovalTests: clean submodule row is forced straight through, no confirmation raised.WorktreeHygieneTests: submodules change the removal mechanics, not the tier — still safe, still removable,removalDiscardsFilesstays false.WorktreeHygiene+WorktreeSweepFeature+WorktreeRemoval; swiftlint and swift-format clean.