Skip to content

Cherry-pick a fix

  • Lesson 14
  • 45 min
  • Module 5: Release and hotfix practice
Lesson 14 of 15 · Git and Team Development EssentialsCherry-pick a fix

What you will be able to do

Fix a bug in a released version without releasing unfinished work. You copy one commit from main to the release branch, and you can prove that nothing else came with it.

Before you startWhat to know and to have readyAbout 45 minutes

What you need to know

What you need on your computer

  • The folder ~/projects/nextia-learning-labs. You cloned it in Pull-request sequence: Set up the practice lab. If it is missing, do step 2 of that section.
  • This lesson uses the practice state hotfix. You make it in Set up the practice state. The setup command deletes your c14-lab folder and makes it again. The state already has release/1.0 and v1.0.0, so you do not need your work from the last lesson.

The help desk uses version 1.0. It calls the network team on extension 4410, but nobody answers. The correct extension is 4401.

Sam already fixed this on main. But main has two other new commits since the release:

Commit Message Can it go into a 1.0 fix?
3cf73f7 Start the ticket export (not finished) No. It is not finished.
aeeb4b6 Fix the network team extension Yes. This is the fix.
3f4d833 Add ticket 105 No. It is new content, not a fix.

If the team merges main into release/1.0, all three commits go into the release. The help desk then gets an unfinished export. The team needs only aeeb4b6.

A hotfix is a fix for a version that people already use. The team releases it as a PATCH version, here v1.0.1.

Cherry-pick copies the change of one commit onto your current branch, as a new commit. Git reads the diff of the commit that you choose, and applies that diff to your branch. The new commit has the same message and the same author. It has a different hash, because it has a different parent.

Two lines of commits. The main line: 465d7ea with the tag v1.0.0, then 3cf73f7, the unfinished export, then aeeb4b6, the fix, then 3f4d833, ticket 105. The release/1.0 line starts at 465d7ea. A dashed arrow labeled cherry-pick -x goes from aeeb4b6 down to 19b64c1 on release/1.0, the copy of the fix. After it, 257b55a has the tag v1.0.1. A dashed arrow labeled merge back goes from 257b55a to a future commit on main.
cherry-pick -x copies only the fix to release/1.0. The unfinished export stays on main. The merge back path is the topic of the next lesson.

The fix and its copy have the same color, because they contain the same change. They are different commits, with different hashes.

Every step happens on a known branch. Check the branch before each command.

Step Current branch PR source → target
1. Find and inspect the fix release/1.0 none
2. Make the hotfix branch release/1.0 → hotfix/1.0.1 none
3. Cherry-pick, check, push hotfix/1.0.1 none
4. Open and review the PR hotfix/1.0.1 hotfix/1.0.1 → release/1.0
5. Merge, tag, push release/1.0 hotfix/1.0.1 → release/1.0 (merged)

The fix goes into the release through a pull request, the same as other changes. The target is release/1.0, not main.

  1. Run the setup script with the scenario hotfix:

    Terminal
    cd ~/projects
    sh nextia-learning-labs/C14/setup.sh hotfix --reset
    cd c14-lab/ticket-notes

    The output starts with Ready:.

  2. Look at all references:

    Terminal
    git log --oneline --graph --all --decorate -5
    Output
    * 3f4d833 (HEAD -> main, origin/main, origin/HEAD) Add ticket 105
    * aeeb4b6 Fix the network team extension
    * 3cf73f7 Start the ticket export (not finished)
    * 465d7ea (tag: v1.0.0, origin/release/1.0) Add escalation rules
    * f330fdc Add ticket 104

    You have the remote branch origin/release/1.0, but no local release/1.0 yet.

Current branch: main. Amira switches to release/1.0. Git makes a local branch that tracks origin/release/1.0:

Terminal
git switch release/1.0
Output
Switched to a new branch 'release/1.0'
branch 'release/1.0' set up to track 'origin/release/1.0'.

Current branch: release/1.0. She lists the commits that are on main but not on release/1.0. A..B means “commits in B that are not in A”:

Terminal
git log --oneline release/1.0..main
Output
3f4d833 Add ticket 105
aeeb4b6 Fix the network team extension
3cf73f7 Start the ticket export (not finished)

A commit message describes the change, but the diff is the change. Amira reads the diff before she copies it:

Terminal
git show aeeb4b6
Output
commit aeeb4b6389efc1d6d75e474e911499fc5fe73e12
Author: Sam Okafor <sam@example.com>
Date: Tue Oct 6 09:12:00 2026 -0400
Fix the network team extension
diff --git a/contacts.txt b/contacts.txt
index 0ce0116..48d016f 100644
--- a/contacts.txt
+++ b/contacts.txt
@@ -1,3 +1,3 @@
Help desk: ext. 4100
Network team: ext. 4410
Network team: ext. 4401
Accounts team: ext. 4300

The commit changes one line in one file. It does what the message says.

A fix depends on an earlier commit when it changes lines that the earlier commit added. If that earlier commit is not on the release, the fix cannot apply there. Amira asks two questions.

Does the release have the lines that the fix changes? She shows contacts.txt as it is on release/1.0:

Terminal
git show release/1.0:contacts.txt
Output
Help desk: ext. 4100
Network team: ext. 4410
Accounts team: ext. 4300

The release has the line Network team: ext. 4410, the same as the line that the fix removes. So the fix can apply.

Does the fix need another commit from main? The only commit between the release and the fix is the export. --stat shows which files it changed:

Terminal
git show --stat 3cf73f7
Output (end)
Start the ticket export (not finished)
export.txt | 2 ++
1 file changed, 2 insertions(+)

The export changed only export.txt. The fix changed only contacts.txt. The fix does not need the export.

4. Make the hotfix branch from the release

Section titled “4. Make the hotfix branch from the release”

Current branch: release/1.0. Amira makes the hotfix branch here, not on main. A branch starts from the commit that you are on, and the fix must go onto the release:

Terminal
git switch -c hotfix/1.0.1
Output
Switched to a new branch 'hotfix/1.0.1'

Current branch: hotfix/1.0.1. The -x option adds a line to the new commit message. The line names the original commit, so that anyone can trace the copy back to it.

Terminal
git cherry-pick -x aeeb4b6
Output
[hotfix/1.0.1 19b64c1] Fix the network team extension
Author: Sam Okafor <sam@example.com>
Date: Tue Oct 6 09:12:00 2026 -0400
1 file changed, 1 insertion(+), 1 deletion(-)

The new commit is 19b64c1. Your hash is different, because you made the copy at a different time. Git kept Sam as the author, because he wrote the change. The full message shows the -x line:

Terminal
git log -1
Output
commit 19b64c13a2bb62fa029d4884fc18af8816ff3f1d
Author: Sam Okafor <sam@example.com>
Date: Tue Oct 6 09:12:00 2026 -0400
Fix the network team extension
(cherry picked from commit aeeb4b6389efc1d6d75e474e911499fc5fe73e12)

The graph now shows the copy on its own line, from v1.0.0:

Terminal
git log --oneline --graph --all --decorate -6
Output
* 19b64c1 (HEAD -> hotfix/1.0.1) Fix the network team extension
| * 3f4d833 (origin/main, origin/HEAD, main) Add ticket 105
| * aeeb4b6 Fix the network team extension
| * 3cf73f7 Start the ticket export (not finished)
|/
* 465d7ea (tag: v1.0.0, origin/release/1.0, release/1.0) Add escalation rules
* f330fdc Add ticket 104

Current branch: hotfix/1.0.1. Amira runs the check, then pushes the branch:

Terminal
sh check.sh
git push -u origin hotfix/1.0.1
Output
PASS: 4 ticket lines checked
To /Users/amira/projects/c14-lab/team-remote.git
* [new branch] hotfix/1.0.1 -> hotfix/1.0.1
branch 'hotfix/1.0.1' set up to track 'origin/hotfix/1.0.1'.

PR: source hotfix/1.0.1 → target release/1.0. This is a simulated pull request, as in Module 3. The reviewer, Sam, checks the diff that the PR adds to the release:

Terminal
git diff release/1.0...hotfix/1.0.1
Output
diff --git a/contacts.txt b/contacts.txt
index 0ce0116..48d016f 100644
--- a/contacts.txt
+++ b/contacts.txt
@@ -1,3 +1,3 @@
Help desk: ext. 4100
-Network team: ext. 4410
+Network team: ext. 4401
Accounts team: ext. 4300

The diff has one changed line. No export, no ticket 105. Sam approves.

Current branch: release/1.0. Amira switches to the target of the PR, and merges it:

Terminal
git switch release/1.0
git merge --no-ff --no-edit hotfix/1.0.1
Output (end)
Merge made by the 'ort' strategy.
contacts.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

Current branch: release/1.0. She tags the release, and pushes the branch and the tag:

Terminal
git tag -a v1.0.1 -m "Help desk notes 1.0.1"
git push
git push origin v1.0.1
Output
To /Users/amira/projects/c14-lab/team-remote.git
465d7ea..257b55a release/1.0 -> release/1.0
To /Users/amira/projects/c14-lab/team-remote.git
* [new tag] v1.0.1 -> v1.0.1

Amira proves that version 1.0.1 has the fix, and nothing else. First, the files that changed between the two versions:

Terminal
git diff v1.0.0 v1.0.1 --stat
Output
contacts.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

Then, the commits that are in 1.0.1 but not in 1.0.0:

Terminal
git log --oneline v1.0.0..v1.0.1
Output
257b55a Merge branch 'hotfix/1.0.1' into release/1.0
19b64c1 Fix the network team extension

Last, the unfinished export is not in the release:

Terminal
git show v1.0.1:export.txt
Output
fatal: path 'export.txt' does not exist in 'v1.0.1'

This error is the result that she wants. The full graph now matches the diagram at the start of the lesson:

git log --oneline --graph --all --decorate -8
* 257b55a (HEAD -> release/1.0, tag: v1.0.1, origin/release/1.0) Merge branch 'hotfix/1.0.1' into release/1.0
|\
| * 19b64c1 (origin/hotfix/1.0.1, hotfix/1.0.1) Fix the network team extension
|/
| * 3f4d833 (origin/main, origin/HEAD, main) Add ticket 105
| * aeeb4b6 Fix the network team extension
| * 3cf73f7 Start the ticket export (not finished)
|/
* 465d7ea (tag: v1.0.0) Add escalation rules
* f330fdc Add ticket 104
* e058b3f Add ticket 103
  1. Run the setup from Set up the practice state. You are on main in ticket-notes.

  2. Switch to the release branch:

    Terminal
    git switch release/1.0

    Current branch: release/1.0. The output includes set up to track 'origin/release/1.0'.

  3. Run git log --oneline release/1.0..main. The output lists three commits. Write down the hash of Fix the network team extension.

  4. Run git show aeeb4b6. Check that it changes only the network team line in contacts.txt.

  5. Run git show release/1.0:contacts.txt. Check that the release has Network team: ext. 4410.

  6. Make the hotfix branch from the release:

    Terminal
    git switch -c hotfix/1.0.1

    Current branch: hotfix/1.0.1.

  7. Copy the fix:

    Terminal
    git cherry-pick -x aeeb4b6

    The output starts with [hotfix/1.0.1 and a new hash.

  8. Run git log -1. The message ends with (cherry picked from commit aeeb4b6...).

  9. Run sh check.sh. The output starts with PASS.

  10. Push the hotfix branch:

    Terminal
    git push -u origin hotfix/1.0.1
  11. Review the simulated PR, source hotfix/1.0.1 → target release/1.0:

    Terminal
    git diff release/1.0...hotfix/1.0.1

    The diff shows one changed line in contacts.txt, and nothing else.

  12. Switch to the target branch, and merge:

    Terminal
    git switch release/1.0
    git merge --no-ff --no-edit hotfix/1.0.1

    Current branch: release/1.0. The output includes Merge made by the 'ort' strategy.

  13. Tag the release:

    Terminal
    git tag -a v1.0.1 -m "Help desk notes 1.0.1"
  14. Push the branch, then the tag:

    Terminal
    git push
    git push origin v1.0.1
  15. Run git diff v1.0.0 v1.0.1 --stat. The output lists only contacts.txt.

  16. Run git show v1.0.1:export.txt. The output is fatal: path 'export.txt' does not exist in 'v1.0.1'.

Sam finds a second bug: the accounts team extension is 4310, not 4300. You play both people. Start from the end of the guided practice.

  1. As Sam, in sam-notes, fix the line in contacts.txt on main. Commit with the message Fix the accounts team extension, and push.
  2. As Amira, in ticket-notes, release the fix as version 1.0.2.
  3. For each step, write the current branch, and the PR source and target if there is one.

Two rules apply. Add ticket 105 is new content, not a fix, so it must not go into the release. The release must not contain export.txt.

Show a model answer
Step Where Current branch PR source → target
Sam pulls, fixes, commits, pushes sam-notes main none (in a real team: fix-accounts → main)
Amira pulls the fix ticket-notes main none
Find and inspect the fix ticket-notes main none
Make the hotfix branch ticket-notes release/1.0 → hotfix/1.0.2 none
Cherry-pick, check, push ticket-notes hotfix/1.0.2 none
Review ticket-notes hotfix/1.0.2 hotfix/1.0.2 → release/1.0
Merge, tag v1.0.2, push ticket-notes release/1.0 hotfix/1.0.2 → release/1.0 (merged)

As Sam, get the latest main, then change Accounts team: ext. 4300 to Accounts team: ext. 4310 in your editor. Commit and push:

Terminal (Sam)
cd ../sam-notes
git pull
git commit -am "Fix the accounts team extension"
git push

As Amira, find the fix. git log lists four commits, because the copy of the first fix has a different hash from Sam’s original. The next lesson explains this.

Terminal
cd ../ticket-notes
git switch main
git pull
git log --oneline release/1.0..main
Output (git log only)
772b7bf Fix the accounts team extension
3f4d833 Add ticket 105
aeeb4b6 Fix the network team extension
3cf73f7 Start the ticket export (not finished)

Pick only 772b7bf (your hash is different). Check it with git show 772b7bf first.

Terminal
git switch release/1.0
git switch -c hotfix/1.0.2
git cherry-pick -x 772b7bf
sh check.sh
git push -u origin hotfix/1.0.2
git diff release/1.0...hotfix/1.0.2
git switch release/1.0
git merge --no-ff --no-edit hotfix/1.0.2
git tag -a v1.0.2 -m "Help desk notes 1.0.2"
git push
git push origin v1.0.2

Prove the result:

Terminal
git diff v1.0.1 v1.0.2 --stat
git log --oneline v1.0.1..v1.0.2
Output
contacts.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
3a01c9f Merge branch 'hotfix/1.0.2' into release/1.0
9e8c739 Fix the accounts team extension

git show v1.0.2:tickets.txt ends with ticket 104, so ticket 105 is not in the release.

Common mistake

The cherry-pick stops with a conflict

Symptom: Sam changed the wording of ticket 105 on main, in a commit called Fix the wording of ticket 105. On hotfix/1.0.1, you try to copy it:

Terminal
git cherry-pick -x bd75493
Output (first lines)
Auto-merging tickets.txt
CONFLICT (content): Merge conflict in tickets.txt
error: could not apply bd75493... Fix the wording of ticket 105

git status shows You are currently cherry-picking commit bd75493 and both modified: tickets.txt.

Cause: the commit depends on a commit that is not on the release. It changes the line for ticket 105, and that line came from Add ticket 105. The release does not have that line:

Terminal
git show release/1.0:tickets.txt
Output
Ticket 101: printer offline. Restarted the spooler.
Ticket 102: password reset email not received.
Ticket 103: shared drive is slow. Moved the user to the new server.
Ticket 104: laptop will not charge. Replaced the power adapter.

Fix:

  1. Stop the cherry-pick. Git puts the branch back as it was:

    Terminal
    git cherry-pick --abort
    git status
    Output
    On branch hotfix/1.0.1
    nothing to commit, working tree clean
  2. Decide with your team. Here the release has no ticket 105, so the release does not need this fix. Do not pick it.

If a real fix depends on an earlier commit, you must also pick that commit, or write the fix again for the release. Do not resolve the conflict by adding lines that the release never had. Always run git show before you pick, so that you see the dependency first.

Common mistake

I picked the wrong commit

Symptom: you copied 3cf73f7 instead of the fix, and pushed the hotfix branch. The PR review shows the wrong file:

Terminal
git diff release/1.0...hotfix/1.0.1 --stat
Output
export.txt | 2 ++
1 file changed, 2 insertions(+)

Cause: the hashes 3cf73f7 and aeeb4b6 are on lines next to each other in git log. It is easy to copy the wrong one.

Fix: on hotfix/1.0.1, before you merge the PR:

  1. Revert the wrong copy:

    Terminal
    git revert --no-edit HEAD
    Output (first line)
    [hotfix/1.0.1 38d6b6e] Revert "Start the ticket export (not finished)"
  2. Copy the correct commit: git cherry-pick -x aeeb4b6.

  3. Review the PR diff again:

    Terminal
    git diff release/1.0...hotfix/1.0.1 --stat
    Output
    contacts.txt | 2 +-
    1 file changed, 1 insertion(+), 1 deletion(-)
  4. Push, and ask for review again.

The diff is now correct. The history of the hotfix branch still shows the mistake and its revert, which is honest and safe.

Check your understanding

Predict: you cherry-pick aeeb4b6 onto hotfix/1.0.1. Does the new commit have the hash aeeb4b6? Does git log release/1.0..main still list aeeb4b6 after you merge the hotfix?

Show the answer

No to the first question. The copy is a new commit with a new hash, because it has a different parent. Yes to the second: release/1.0 contains the copy, not aeeb4b6 itself, so git log release/1.0..main still lists aeeb4b6. The -x line is how a person traces the copy back to the original.

Check your understanding

Practical check: prove that v1.0.1 contains the fix, and does not contain the unfinished export or ticket 105.

Show the answer
  • git show v1.0.1:contacts.txt shows Network team: ext. 4401.
  • git diff v1.0.0 v1.0.1 --stat lists only contacts.txt.
  • git log --oneline v1.0.0..v1.0.1 lists only the copy of the fix and the merge commit.
  • git show v1.0.1:export.txt fails with does not exist, and git show v1.0.1:tickets.txt ends with ticket 104.

Recap

  • Cherry-pick copies the change of one commit onto your current branch, as a new commit with a new hash.
  • Use git log --oneline release/1.0..main to find the fix, and git show to read it before you copy it.
  • Check dependencies: the release must have the lines that the fix changes, and the fix must not need other commits from main.
  • Make hotfix/1.0.1 from release/1.0, pick with -x, then open a PR with the target release/1.0.
  • Tag the merged result v1.0.1, push the tag, and prove the release with git diff v1.0.0 v1.0.1 --stat.

Next: Keep histories consistent