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
What each option survivesA stash is removed by one unconfirmed command and exists only on the machine that created it. A committed branch pushed to the remote survives a lost laptop, a reclone and a mistaken cleanup. A worktree is local but leaves the work checked out, so nothing has to be restored at all.StashPushed branchsurvives a lost machinenoyessurvives a mistaken clearnoyesvisible to colleaguesnoyescommands to createonethreetwo extra commands buy every row above them

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/*'
Choosing by how long the work will sitUnder an hour, the stash's convenience outweighs its fragility. Longer than that and the lack of a backup starts to matter. When the interruption is itself a piece of work, a worktree means neither task has to be set aside.How long will the work sit before you return to it?minutesStashfast, acceptable riskhours or daysCommit and pushsurvives everythingit is parallel workWorktreenothing is set asidethe middle answer is the one most often replaced by the left one out of habit

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 --ignored before 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
The cost and the cleanup for eachA stash costs one command and leaves an entry that must be popped or dropped. A WIP branch costs three and leaves a branch to delete locally and remotely. A worktree costs a directory and an install, and leaves a directory to remove properly.Stashone commandpop or dropno backupWIP branchthree commandsdelete both copiesbacked upWorktreeone command plus installworktree removestays checked outall three leave something behind; only one of them leaves it somewhere safe

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.