Git & Command Line · Lesson 11 of 12

Cherry-pick, Bisect and Reflog

Copy commits between branches with git cherry-pick, find the commit that introduced a bug with git bisect run, and recover lost work with git reflog.

  • Advanced
  • 16 min read
  • 3 objectives

Before this lessonLesson 10: Stash, Tags and Releases

What you will learn

  • Backport a fix with cherry-pick
  • Find a bad commit with bisect, manually and automatically
  • Recover reset commits and deleted branches with reflog

Your Progress

0 of 12 lessons 0%

  • Lessons0 / 12
  • Completed0
  • Est. time left~ 3 hours

Create a free account to keep your progress on every device.

Tip: pressing Next marks this lesson complete automatically.

The undo lesson listed these three tools in one line each. They deserve more, because they solve some of the most stressful moments in a developer's week: "we need that fix on the release branch now", "something broke in the last 200 commits" and "I think I just deleted my work".

cherry-pick: copy one commit

git cherry-pick <sha> takes the change introduced by one commit and applies it as a new commit on your current branch. The classic use is a backport: a bug fix landed on main, and you need it on a release branch without also shipping unfinished features.

git log --oneline main
Output
f06d7f8 Fix login redirect bug
9162aba Add dark mode
3e85e13 Release 1.0

We want the fix on release-1.0, but not dark mode:

git switch release-1.0
git cherry-pick f06d7f8
git log --oneline
Output
[release-1.0 8d40c99] Fix login redirect bug
 Date: Wed Sep 23 10:09:20 2026 +0530
 1 file changed, 1 insertion(+)
 create mode 100644 security.txt
8d40c99 Fix login redirect bug
3e85e13 Release 1.0

Notice the new hash 8d40c99: it is a copy, not the same commit. Useful options:

  • git cherry-pick -x <sha>: adds "(cherry picked from commit ...)" to the message, so reviewers can trace the backport.
  • git cherry-pick A..B: a range (excluding A, including B).
  • git cherry-pick -n <sha>: apply the changes without committing.
  • On conflict: fix, git add, then git cherry-pick --continue, or --abort.

bisect: binary search for a bug

A test passed last week and fails today, with dozens of commits in between. Checking each one is slow. git bisect does a binary search: you mark one good and one bad commit, Git checks out the middle, you say good or bad, and it halves the range each time. 1,000 commits take about 10 steps.

git bisect start
git bisect bad                 # current commit is broken
git bisect good v1.0.0         # this old one worked
# Git checks out a commit in the middle; test it, then:
git bisect good                # or: git bisect bad
# ...repeat until Git names the first bad commit...
git bisect reset               # go back to where you started

If you cannot test a particular commit (it doesn't build), git bisect skip moves on to a neighbour.

bisect run: let a script decide

If you can express "is this commit good?" as a command, Git will do the whole search for you. The rule: exit code 0 means good, 1 to 127 (except 125) means bad, and 125 means skip. That is why the shell scripting lesson matters. Here a calculator was accidentally changed from a+b to a-b somewhere in the last seven commits:

git bisect start HEAD HEAD~7          # bad, then good
git bisect run grep -q "a+b" calc.txt
git bisect reset
Output
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[f4632254cef448c04f6b3e615557f5e392a5c160] Add note 3
running 'grep' '-q' 'a+b' 'calc.txt'
Bisecting: 1 revision left to test after this (roughly 1 step)
[d50d1f0e6400cf35bbff303361559a75803dbcdf] Add note 4
running 'grep' '-q' 'a+b' 'calc.txt'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[1ab164c949cff8dc9f55b1114dc68cec72b89781] Refactor calculator
running 'grep' '-q' 'a+b' 'calc.txt'
1ab164c949cff8dc9f55b1114dc68cec72b89781 is the first bad commit
commit 1ab164c949cff8dc9f55b1114dc68cec72b89781
Author: Ada <ada@stackcone.com>
Date:   Wed Sep 23 10:09:13 2026 +0530

    Refactor calculator

 calc.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit
Previous HEAD position was 1ab164c Refactor calculator
Switched to branch 'main'

In a real project the check is usually your test suite: git bisect run npm test -- search.test.js or git bisect run pytest tests/test_cart.py. Small, focused commits make bisect point at a tiny diff, which is one more reason to keep history clean.

reflog: Git's flight recorder

Every time HEAD moves (commit, checkout, reset, rebase, merge, cherry-pick), Git writes a line to the reflog. It is local to your machine and entries are kept for about 90 days by default. Commits that no branch points to are not deleted immediately, so the reflog can bring them back.

Here we "accidentally" throw away two commits with a hard reset:

git reset --hard HEAD~2
git log --oneline
git reflog -4
Output
HEAD is now at 3e85e13 Release 1.0
3e85e13 Release 1.0
3e85e13 HEAD@{0}: reset: moving to HEAD~2
f06d7f8 HEAD@{1}: checkout: moving from release-1.0 to main
8d40c99 HEAD@{2}: cherry-pick: Fix login redirect bug
3e85e13 HEAD@{3}: checkout: moving from main to release-1.0

HEAD@{1} is where we were just before the reset, f06d7f8. Jump back:

git reset --hard HEAD@{1}
git log --oneline
Output
HEAD is now at f06d7f8 Fix login redirect bug
f06d7f8 Fix login redirect bug
9162aba Add dark mode
3e85e13 Release 1.0

Recovering a deleted branch or bad rebase

git branch -D experiment            # oops
git reflog | grep experiment        # find its last commit, e.g. 7c1d2e3
git branch experiment 7c1d2e3       # recreate the branch there

# undo a rebase you regret
git reflog                          # find 'rebase (start)' and the line before it
git reset --hard HEAD@{5}           # the entry just before the rebase began

# every branch has its own reflog too
git reflog show feature
git reset --hard ORIG_HEAD          # ORIG_HEAD = where HEAD was before the last reset/merge/rebase

Recap

  • cherry-pick copies a commit (new hash) onto the current branch; add -x for backports.
  • bisect binary-searches history; bisect run automates it with a script's exit code.
  • Exit 0 is good, 125 is skip, other non-zero codes up to 127 are bad.
  • reflog records every HEAD move for about 90 days; reset or branch back to any entry.
  • Only committed or stashed work can be recovered.
# Write your solution here

Finished reading? Mark this lesson complete to track your progress.

Up next · Lesson 12CI with GitHub ActionsSet up continuous integration with GitHub Actions: write workflow YAML that runs tests on every push and pull request, with caching, matrices and secrets.