Practical Git Workflows #6: stash, cherry-pick, bisect — Everyday Tools
If the branching strategy, rebase, commit cleanup, conflict resolution, and PR operations covered so far are the skeleton of a workflow, the three tools in this post fill the gaps between them. You will not use them every day, but not knowing them hurts at the moment you need them. When urgent work interrupts you mid-task, when exactly one commit has to move to another branch, when nobody knows since when a bug has existed — those are the moments, and stash, cherry-pick, and bisect answer them respectively.
It runs in seven parts.
- #1 Branching strategies — GitHub Flow and trunk-based
- #2 rebase vs merge — decision criteria and the golden rule
- #3 Interactive rebase — cleaning up commits with squash and fixup
- #4 Resolving conflicts — structure, mergetool, and rerere
- #5 Running Pull Requests — review size, commit messages, and draft PRs
- #6 stash, cherry-pick, bisect — everyday tools ← this post
- #7 Monorepos and Git — sparse-checkout, submodules, and LFS
We will go through the three tools one by one: the situation, the basic usage, and the common pitfalls.
git stash — setting work aside for a moment #
You are in the middle of work on a feature branch when an urgent review request or hotfix cuts in. The working directory is full of changes that are too half-done to commit and too valuable to throw away. stash pushes those in-between changes into a temporary shelf and leaves the working directory clean.
git stash push -m "wip: cart validation logic"
# Working directory is clean — switch branches, hotfix freely
git stash list
# stash@{0}: On feature/cart: wip: cart validation logic
git stash pop # restore and remove from the shelfRunning plain git stash without a message works too, but the moment two or more entries pile up, unnamed stashes become indistinguishable. It is worth building the -m habit from the start.
There are two restore commands. pop applies the stash and removes it from the shelf at the same time, while apply only applies it and leaves it on the shelf. When you want to try the same changes on several branches, apply is the right one. One thing to watch: if a conflict occurs during pop, the stash is not deleted automatically. After resolving the conflict, clean up with git stash drop yourself to keep the shelf tidy.
A basic stash only includes tracked files. To shelve newly created untracked files as well, add the -u option. And if the stashed work has grown large and drifted far from its original branch, git stash branch <branch-name> creates a new branch based on the stash point and restores your work there safely.
git worktree is worth considering instead of stash. Running something like git worktree add ../hotfix main checks out another branch of the same repository into a separate directory. You handle the hotfix in the folder next door without touching the directory you are working in, so the shelve-and-restore procedure itself disappears.git cherry-pick — moving just one commit #
If merge is the tool that combines the entire history of a branch, cherry-pick is the tool that takes the changes of one specific commit and reapplies them as a new commit on the current branch.
The classic scenario is backporting a hotfix to a release branch. A bug fixed on main is also needed on an already-shipped release branch, but you cannot merge everything else on main along with it. So you pick just the fix commit.
git switch release/2.3
git cherry-pick -x 4f9a2c1
# [release/2.3 8d3e5b7] fix(auth): correct token expiry validation
# (cherry picked from commit 4f9a2c1...)The -x option automatically appends a reference to the original commit in the message. That one line is what lets you trace later which branches a fix has landed on, so treat it as the default when backporting to public branches.
A run of consecutive commits can be picked as a range: git cherry-pick A..B applies the commits after A up to B in order. If a conflict occurs along the way, resolve it with the procedure from the previous post and continue with git cherry-pick --continue, or abandon the whole run with --abort to return to the state before it started.
The thing to watch is overuse. cherry-pick duplicates the same change into two histories as commits with different hashes. Once cherry-picking between branches becomes routine, it gets hazy which branch has what, and later merges can surface the same change twice and cause confusion. The normal path is still merge or rebase; keep cherry-pick as the tool for exceptions like backports and it stays safe.
git bisect — finding the commit that introduced a bug #
There is a bug of the “it worked until last week and now it does not” kind. There are dozens of commits in between, and nobody knows which one is the culprit. Checking commits one by one means dozens of checks, but bisect solves this with binary search. It checks the middle commit between the good and the bad, then halves the range based on the result.
git bisect start
git bisect bad # the current commit is broken
git bisect good v2.3.0 # this tag was fine
# Bisecting: 10 revisions left to test after this (roughly 4 steps)
# Git checks out the middle commit — verify behavior, then enter the verdict
git bisect good # this commit is fine
# Bisecting: 5 revisions left to test after this (roughly 3 steps)
git bisect bad # this commit is broken
# ...repeat...
# 4f9a2c1 is the first bad commitA culprit hiding among 20 commits is identified within five verdicts. Even with hundreds of commits, the number of checks stays around ten. And here the principles built up in the previous posts pay off directly: if commits are small and each one was kept buildable, then the moment you identify the bad commit, its diff is small enough to point straight at the offending code.
The verdict can also be automated with a command. If you have a script that exits with 0 on success and non-zero on failure, the whole process runs unattended.
git bisect start HEAD v2.3.0
git bisect run pytest tests/test_auth.py
# Git repeats checkout and verdict on its own, then reports the first bad commitWhen the search is done, return to your original branch with git bisect reset. During bisect, HEAD has moved onto past commits, so forgetting the reset leads to the accident of continuing work at the wrong point in history.
The thread running through all three #
Put the three side by side and one common trait stands out: the cleaner the history, the more precise these tools become.
- cherry-pick transplants cleanly when a commit contains exactly one change. A commit with several changes mixed in drags unneeded edits along with it.
- bisect runs smoothly when every commit builds. With “wip” commits mixed in, the search keeps stalling at unverifiable points.
- Even stash comes up less often on teams with small commit units, simply because there is less half-done work lying around at any moment.
The habit of organizing commits into meaningful units and shipping small PRs does not end with itself — it comes back as the performance of these tools.
Wrap-up #
The takeaways from this post are three.
- stash is the temporary shelf for in-between changes. Name entries with
-m, remember the manual cleanup after a pop conflict, and consider worktree if you switch often. - cherry-pick is the backport tool that reapplies a single commit. Record the origin with
-x, and use it with restraint so it never replaces the normal merge path. - bisect pinpoints the commit that introduced a bug by binary search. With a test script,
git bisect runautomates the whole process.
The next post, “Practical Git Workflows #7: Monorepos and Git — sparse-checkout, Submodules, and LFS”, is the final part of the series. It covers the problems you meet as a repository grows: partial checkout of a huge repository, repositories inside repositories, and managing large files — and with that, the series concludes.