Stash vs worktree vs WIP commit Jump to heading
The stash is reached for reflexively because it is the shortest command, not because it is the right tool. For a two-minute interruption it genuinely is; for anything longer it is unbacked storage on one machine that a single unconfirmed command removes. A scratch commit on a branch is barely more effort and survives everything, and a worktree avoids the interruption altogether. This page compares them honestly so the choice stops being a habit, within stashing and work-in-progress recovery.
When to use this approach Jump to heading
- You are about to be interrupted and are deciding where to put the work.
- A stash has been lost and you are reconsidering the habit.
- Work in progress needs to survive a laptop failure or a machine rebuild.
- Someone else needs to see the half-finished work.
- If the interruption is thirty seconds, do nothing at all β leave the tree as it is.
Step 1 β Compare what each one survives Jump to heading
Durability is the axis that matters and the one nobody checks until it is too late.
# A stash: local ref, not pushed, no backup
git stash push -u -m 'probe'
git ls-remote origin 'refs/stash' | wc -l # 0 β it is not on the remote
git stash pop # A commit on a branch: pushable, backed up, visible
git switch -c wip/probe && git commit -am 'wip: probe' && git push -u origin HEAD
git ls-remote origin 'refs/heads/wip/probe' | wc -l # 1
git push origin --delete wip/probe && git switch - && git branch -D wip/probe # A worktree: the work stays checked out, in its own directory
git worktree add --detach ../probe HEAD && git worktree list && git worktree remove ../probe Step 2 β Match the tool to the length of the interruption Jump to heading
# Minutes: stash, and pop it when you come back
git stash push -u -m 'mid-refactor, back in five' # Hours to days: a commit on a branch, pushed
git switch -c wip/PAY-812 && git add -A
git commit -m 'wip: refund window clamp, incomplete' && git push -u origin HEAD # Concurrent work: a worktree, so nothing is set aside at all
git worktree add -b fix/PAY-931 ../app-hotfix origin/main # Verification: what is currently set aside, and where?
git stash list; git worktree list; git branch --list 'wip/*' Step 3 β Make the WIP commit path as cheap as the stash Jump to heading
The objection to committing is effort, and it is removable.
# An alias that does the whole thing in one word
git config --global alias.wip '!f() { git switch -c "wip/$(date +%s)" && git add -A \
&& git commit -m "wip: ${1:-work in progress}" && git push -u origin HEAD; }; f' git wip 'refund window clamp, half done' # And a matching return: come back, uncommit, keep working
git config --global alias.unwip '!git reset --soft HEAD~1'
git unwip # Verification: the work is on the remote and back in the working tree
git log --oneline -1 && git status --short | head -3 reset --soft HEAD~1 moves the branch back one commit while leaving everything staged, which restores exactly the state you were in before the WIP commit β the same experience as popping a stash, with a backup on the remote in the meantime.
Step 4 β Know what each one does not capture Jump to heading
# Stash: untracked files need -u, ignored files need -a (rarely wanted)
git stash push -m 'tracked only'; git status --short # untracked still present # WIP commit: hooks do not run with --no-verify, and .gitignore still applies
git add -A && git status --short --ignored | head -3 # Worktree: dependencies and ignored config do not come with it
ls ../app-hotfix/node_modules 2>/dev/null || echo "needs its own install" # Verification: nothing you care about is outside whichever mechanism you chose
git status --short --untracked-files=all | head SAFETY WARNING β none of the three capture files outside the repository, and a WIP commit does not capture ignored files. Local environment files, credentials and generated configuration are frequently exactly what makes a half-finished state work, and they are exactly what is excluded. Check
git status --ignoredbefore assuming a branch is a complete snapshot of what you had.
Step 5 β Clean up whichever you chose Jump to heading
Each leaves something behind, and each has a different tidy-up.
# Stash: the entry, which should not outlive the day
git stash list # WIP branches: delete once the work has landed properly
git branch --list 'wip/*' --format='%(refname:short) %(committerdate:relative)'
git push origin --delete wip/1737200000 2>/dev/null; git branch -D wip/1737200000 # Worktrees: remove rather than deleting the directory
git worktree list; git worktree remove ../app-hotfix # Verification: nothing is left over
git stash list | wc -l; git branch --list 'wip/*' | wc -l; git worktree list | wc -l Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Is a WIP commit not untidy in history? Jump to heading
Only if it survives to the default branch, and it should not. A WIP commit is a container for work in progress; when the work is finished it is squashed, amended or rebased into whatever shape review deserves. The techniques are in using fixup commits and autosquash during review.
Does pushing a WIP branch bother anyone? Jump to heading
Not if the naming makes the intent obvious. A wip/ prefix signals that nobody should review or build on it, and most forges can be configured to skip pipeline runs for such branches β which also removes the objection that WIP pushes waste CI time.
What about the stashβs advantage of not needing a branch name? Jump to heading
That is genuine, and it is why the alias in Step 3 generates one. The naming friction is real; it is also solvable in one line, which leaves the durability difference as the only thing actually distinguishing the two options.
Related Jump to heading
- Stashing & Work-in-Progress Recovery β the parent topic and what a stash stores.
- Git Worktrees & Parallel Development β the third option, in depth.
- Recovering a Dropped Stash β what the durability difference costs when it bites.