Skip to content

feat(remove): back up uncommitted changes before --force discards them - #4545

Open
worktrunk-bot wants to merge 3 commits into
mainfrom
feat/remove-force-backup
Open

worktrunk-bot wants to merge 3 commits into
mainfrom
feat/remove-force-backup

Conversation

@worktrunk-bot

Copy link
Copy Markdown
Collaborator

wt remove --force now saves a worktree's uncommitted changes before discarding them, using the same refs/wt-backup/<branch> ref and reflog that the squash backup in wt merge / wt step squash already uses. The output then prints the backup and a recovery command:

▲ Discarding uncommitted changes (--force):
  ?? devbox.lock
↳ Backup created @ refs/wt-backup/feature-untracked (690353c)
↳ To recover in a new worktree, run wt switch --create --base 690353c… --no-hooks recovery/690353c

Closes #4544.

What gets saved

  • Staged, unstaged, and untracked files go into the snapshot, which is what git add -A would stage. The snapshot is built through a TempIndex, so the real index and the stash list are left alone.
  • Ignored files, such as build output, are not saved. For submodules, only the checked-out commit is recorded; uncommitted changes inside a submodule are not saved.
  • The backup commit's parent is the worktree's HEAD, so the branch tip also stays reachable after -D deletes the branch. On an unborn branch the commit has no parent.
  • The backup is taken only when --force actually discards something. A clean --force removal creates no ref.

Decisions for review

  • Always on, no flag. This follows the issue thread: a flag would grow the CLI, and always saving means -f loses nothing you can't get back.
  • A failed backup refuses the removal. The error is Failed to back up uncommitted changes; worktree not removed. That follows the data-safety guidance in AGENTS.md, but it also means wt remove -f can no longer remove a worktree that git can't snapshot (for example, a corrupt index). The alternative is to warn and continue.
  • Cost. Untracked files that aren't ignored are written as blobs. A large unignored artifact makes the removal slower and grows the object store until the reflog entry expires. Nothing prunes refs/wt-backup/ today, and this PR doesn't change that.

Code

create_safety_backup and the new create_removal_backup (src/git/repository/working_tree.rs) share a record_backup helper for the commit-tree / update-ref step. The helper looks up the branch name without the cache, and falls back to symbolic-ref so an unborn branch is still named. The removal side lives in stage_worktree_removal (src/git/remove.rs). That function runs right before the worktree is renamed into trash, so it covers wt remove, wt merge --remove, and the foreground path.

Tests

  • test_remove_force_with_untracked_files now checks that the backup ref holds the untracked file's content, that its parent is the removed branch tip, and that an ignored file is excluded.
  • A new unit test covers the unborn-branch case.
  • The four remove_force_with_* snapshots and the wt remove --help snapshot gain the new lines. The docs (wt remove help, FAQ) mention the backup.

`wt remove --force` now saves the worktree's staged, unstaged and untracked
changes (ignored files excepted) as a commit under `refs/wt-backup/<branch>`
before removing it, the same ref and reflog the squash backup uses, and prints
a `wt switch --create --base <sha>` command to recover them. The snapshot is
built through a temporary index, so the real index is untouched; its parent
is the worktree's HEAD, which keeps the branch tip reachable after `-D`.

A failed backup refuses the removal rather than lose the changes.

Closes #4544

Co-Authored-By: Claude <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small follow-ups, which I'll push as one commit:

Comment thread src/git/remove.rs Resolved
Comment thread src/cli/mod.rs Resolved
worktrunk-bot and others added 2 commits October 21, 2026 08:41
…dule exception

Move the backup/recovery hint block into SafetyBackup::print_recovery_hints
so wt remove --force and wt step squash print the same thing, and name
changes inside submodules alongside ignored files as not saved by the
--force backup.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stash on wt remove -f

1 participant