Skip to main content
Git & GitHub · lesson 11 of 12
Git & GitHub intermediate Lesson 11 of 12

Cherry-picking a Hotfix and Tagging a Release

Port a single commit from an older branch onto main with git cherry-pick, then mark the result with a real annotated tag and inspect it.

By SkillByExample · Published

git cherry-pick tag

cherry-pick moves one commit’s changes to a different branch without touching anything else around it — the tool for “just this one fix,” not a whole merge.

1) A fix committed on the wrong branch

$ git checkout -b hotfix/divide-by-zero c3f2b20
$ # ...add a zero-denominator guard to divide()...
$ git commit -am "Guard divide() against zero denominator"
$ git log --oneline -1
89b2ffe Guard divide() against zero denominator

The hotfix branch started from an older commit (c3f2b20), not main’s current tip — a realistic shape for a fix made against a release branch that needs to also land on main.

2) Cherry-pick it onto main

$ git checkout main
$ git cherry-pick 89b2ffe
$ git log --oneline -2
Auto-merging mathlib.mjs
[main 1ae306e] Guard divide() against zero denominator
1ae306e Guard divide() against zero denominator
39e53d1 Add .gitignore

Same message, same diff intent, but a new commit (1ae306e) with main’s current tip as its parent — main’s own history since the hotfix branched off (everything through 39e53d1) is completely untouched, unlike a merge which would pull in the hotfix branch’s own ancestry too.

3) Mark this point with an annotated tag

$ git tag -a v1.0.0 -m "First stable release"
$ git tag -n
v1.0.0          First stable release
$ git show v1.0.0 --stat -s
tag v1.0.0
Tagger: Dev <dev@example.com>
Date:   Mon Sep 14 15:47:17 2026 +0530

First stable release

commit 1ae306ee9b692ff12cd8b2b63bd6c306af00d35c
Author: Dev <dev@example.com>

    Guard divide() against zero denominator

-a makes this a real object with its own tagger and message — git show prints that metadata first, then falls through to the commit it points at. A tag doesn’t move the way a branch does; v1.0.0 will always mean exactly this commit.

Practice

  1. Create a lightweight tag (git tag v1.0.0-lw, no -a/-m) alongside the annotated one, then compare git show v1.0.0-lw against git show v1.0.0 — the lightweight one skips straight to the commit, no tagger block.

  2. Cherry-pick a commit that conflicts: edit the same line on both branches before picking, then resolve the conflict the same way you would a merge conflict (git add, then git cherry-pick --continue).

    Solution
    $ git cherry-pick <hash>
    # CONFLICT (content): Merge conflict in ...
    $ # ...edit the file, remove markers...
    $ git add <file>
    $ git cherry-pick --continue

    Note the continuation command is cherry-pick --continue, not commit — same pattern as rebase --continue, different from a plain merge’s commit.

  3. Cherry-pick a range of commits at once with git cherry-pick <hash1>^..<hash2> and confirm each one lands as its own separate new commit, in order.

  4. Push the tag with git push origin v1.0.0 and confirm a plain git push (run first, without the tag) does not send it — tags need an explicit push.

  5. Delete a tag locally with git tag -d v1.0.0-lw and confirm git tag -n no longer lists it, while the commit it pointed to is completely unaffected.

Frequently Asked Questions

How is git cherry-pick different from git merge or git rebase?
Merge and rebase operate on ranges of commits between two branches. cherry-pick takes exactly one specific commit (by hash) and replays just that commit's diff onto your current branch — the rest of its origin branch's history is never touched.
Why did the cherry-picked commit get a new hash instead of keeping 89b2ffe?
A commit's hash includes its parent. Replayed onto main's tip (a different parent than the hotfix branch had), the content is identical but the object is not — so, like rebase, cherry-pick always produces a new hash even when the diff applies cleanly.
What's the difference between an annotated tag (-a) and a lightweight tag?
A lightweight tag (git tag v1.0.0, no -a) is just a name pointing directly at a commit — like a branch that never moves. An annotated tag is its own object with a tagger, date, and message, the same way a commit has an author; git show on it prints that metadata before the commit it points to.
Do tags get pushed automatically with git push?
No — tags are not included in a plain git push. They need git push origin v1.0.0 for one tag, or git push --tags for all of them, pushed as a separate, deliberate step.