Rebase and interactive rebase work with the set of commits on a branch: they break them down, reorder them, reapply them. git cherry-pick does something far more surgical: it takes one specific commit, wherever it is, and applies its change onto the branch you are on now, creating a new commit.
The name says it all: cherry-picking is choosing the cherries one by one. And like every surgical tool, it is excellent in hands that know when to use it and dangerous in hands that use it out of habit. A badly placed cherry-pick duplicates work, muddles the history and causes absurd conflicts months later.
Ana needs it today. She has fixed the bug on main whereby the focus is lost when a task is deleted, and version 1.x, still deployed at the client, has the same bug but lives on a separate maintenance branch which cannot receive everything that is on main. She wants that commit and nothing else.
Contents
- What
git cherry-pickdoes - Ana's case: taking a fix over to maintenance
- Several commits and ranges
- Useful options:
-n,-x,-e,-s - Conflicts while cherry-picking
- Rescuing a commit from an abandoned branch
- The problem of duplicates
- Detecting duplicates:
git cherryand--cherry-mark - When NOT to use cherry-pick
- What
git cherry-pick does
git cherry-pick doesGit takes the diff that <sha> introduces (that is, the difference between that commit and its parent), applies it onto the current state of your branch and creates a new commit with that change.
The key word, once again, is new. Just as with rebase (lesson 05-01), and for the same reason to do with the data model of lesson 01-04: the resulting commit has a different parent, therefore different content, therefore a different hash. It is not "the same commit on two branches": they are two different commits that happen to introduce the same change.
gitGraph commit id: "1a4c8d6" commit id: "8b6d3c2" branch maintenance/1.x commit id: "e4f7b91" checkout main commit id: "c5d9b1e" commit id: "7d3a8f4" checkout maintenance/1.x cherry-pick id: "7d3a8f4"
The diagram lies a little on purpose, because gitGraph reuses the original commit's label. In reality, what appears on maintenance/1.x is a different commit, with hash b9e2c17, introducing the same change as 7d3a8f4. Remember that every time you see a cherry-pick diagram: the dotted arrow means "the same change", never "the same object".
What is preserved and what is not, to complete the mental table you started in 05-01:
| Element | Preserved? |
|---|---|
| Message | Yes (editable with -e) |
| Author and email | Yes: it still belongs to whoever wrote it |
| Author date | Yes |
| Committer and their date | No: you become the committer, now |
| Content of the change | Yes, if there is no conflict |
| Parent and hash | No |
Preserving authorship matters: when you take a colleague's fix over to another branch, the credit remains theirs.
- Ana's case: taking a fix over to maintenance
The task-manager repository has two live lines:
main, where version 2 is being developed.maintenance/1.x, which reproduces what is deployed at the client and receives fixes only.
Ana fixed the focus bug on main:
7d3a8f4 (HEAD -> main, origin/main) Return focus to the text field after deleting a task c5d9b1e Extract the task element creation into its own function 8b6d3c2 Store the tasks in localStorage
The commit is small and self-contained, which is exactly what makes a commit a good candidate for cherry-picking:
commit 7d3a8f4...
Author: Ana Ferrer <[email protected]>
Date: Wed Jul 29 11:42:08 2026 +0200
Return focus to the text field after deleting a task
diff --git a/app.js b/app.js
--- a/app.js
+++ b/app.js
@@ -48,6 +48,7 @@ function deleteTask(id) {
tasks = tasks.filter(function (t) { return t.id !== id; });
saveTasks();
renderList();
+ document.getElementById('new-task').focus();
}Now for the transplant:
e4f7b91 (HEAD -> maintenance/1.x, origin/maintenance/1.x) Fix the date format in the list 8b6d3c2 Store the tasks in localStorage
[maintenance/1.x b9e2c17] Return focus to the text field after deleting a task Date: Wed Jul 29 11:42:08 2026 +0200 1 file changed, 1 insertion(+)
commit b9e2c17...
Author: Ana Ferrer <[email protected]>
Date: Wed Jul 29 11:42:08 2026 +0200
Return focus to the text field after deleting a task
(cherry picked from commit 7d3a8f4c9b1e5d2a8f7c3b6e9d4a1c8f5b2e7d3a)
diff --git a/app.js b/app.js
...A new hash (b9e2c17), Ana's authorship intact, an identical change, and a line at the end of the message saying where it came from. That line was put there by -x, and it deserves its own section.
- Several commits and ranges
git cherry-pick accepts several individual commits and ranges.
They are applied in the order you write them, not in chronological order. If they depend on one another, write them in the order they were made or you will have avoidable conflicts.
Ranges use the same two-dot notation from lesson 02-06, and with the same trap:
# From the commit AFTER A, up to B (all inclusive except A)
git cherry-pick A..B
# From A INCLUDED up to B
git cherry-pick A^..B| Expression | Commits applied |
|---|---|
A..B |
Those after A up to B. A is NOT included |
A^..B |
The same, but A is included |
B |
Only B |
B~3..B |
The last three up to B |
--stdin |
Those passed to it on standard input, one per line |
An example with real names: Bruno wants to take over to maintenance the three fix commits he made on feature/csv-export, which run from 6e1d9f3 to 9c7e5a1:
[maintenance/1.x 3a8f2d5] Add the conversion of tasks to CSV format [maintenance/1.x 7b4e9c1] Download the generated CSV from the browser [maintenance/1.x d62a4f8] Add the export button to the action bar
Three new commits with three new hashes, in the right order.
An important warning: a cherry-pick range does not include merge commits. If the range contains a merge, Git skips it silently (or fails, depending on the case). Transplanting a merge commit requires -m to choose the parent against which to compute the diff, just as in revert (lesson 05-06), and it is hardly ever what you want. If your range has merges in it, what you were probably after was a merge or a rebase.
- Useful options:
-n, -x, -e, -s
-n, -x, -e, -s| Option | Long name | What it does |
|---|---|---|
-n |
--no-commit |
Applies the changes to the working tree and the index, without committing |
-x |
— | Adds (cherry picked from commit <sha>) to the message |
-e |
--edit |
Opens the editor so you can modify the message |
-s |
--signoff |
Adds a Signed-off-by: Name <email> line |
--ff |
— | If the commit is a direct child of HEAD, does a fast-forward instead of creating a new one |
--strategy-option |
-X ours / -X theirs |
Automatic conflict resolution in favour of one side |
--allow-empty |
— | Allows the commit to be created even if it contributes no changes |
-n (--no-commit) is for grouping. If you want five small commits to arrive on the destination branch as one single one:
The three changes end up staged in the index and you decide when and with what message to commit them. If you have second thoughts halfway, git cherry-pick --abort undoes everything accumulated.
-x is essential on maintenance branches and is the reason the option exists. It leaves a note in the message that lets you answer, months later, the question "where did this commit come from?":
Return focus to the text field after deleting a task (cherry picked from commit 7d3a8f4c9b1e5d2a8f7c3b6e9d4a1c8f5b2e7d3a)
With that line, git show 7d3a8f4 takes you to the original and you can see its full context. Without it, the commit appears on the maintenance branch with no possible explanation.
Careful with one nuance: -x only makes sense when the source commit is public. If you copy a commit from a local branch you are about to delete, the reference will point at a hash nobody else can resolve, and the note confuses more than it helps. Git's documentation is explicit on this: use it to copy from one public branch to another.
-s adds the Signed-off-by: line, a convention of projects that require contributors to certify the origin of their contributions (the Linux kernel's Developer Certificate of Origin is the canonical case). It is not a cryptographic signature; those are GPG-signed tags and commits, which are covered in lesson 08-05.
- Conflicts while cherry-picking
A cherry-pick applies a patch onto a context that may have changed. It is quite normal for it to conflict, especially when the destination branch has diverged a lot.
Auto-merging app.js CONFLICT (content): Merge conflict in app.js error: could not apply 7d3a8f4... Return focus to the text field after deleting a task hint: After resolving the conflicts, mark them with hint: "git add/rm <pathspec>", then run hint: "git cherry-pick --continue". hint: You can instead skip this commit with "git cherry-pick --skip". hint: To abort and get back to the state before "git cherry-pick", hint: run "git cherry-pick --abort".
The mechanics of resolution — markers, git status, editing, git add — are those of lesson 03-05 and do not change. What is specific is how you get out:
| Command | What it does |
|---|---|
git cherry-pick --continue |
Commits with your resolution and carries on with the next one in the range |
git cherry-pick --skip |
Discards that commit and carries on with the rest |
git cherry-pick --abort |
Cancels the whole operation and returns the branch to its initial state |
git cherry-pick --quit |
Leaves the cherry-pick state but keeps what has already been applied |
And here, good news: in a cherry-pick, ours and theirs are NOT inverted. ours is your destination branch (where you are) and theirs is the commit you are bringing in, which is the intuitive arrangement. The inversion of lesson 05-01 is a peculiarity of rebase, not a general rule.
# During a cherry-pick conflict
git checkout --ours app.js # the version from my destination branch
git checkout --theirs app.js # the version from the commit I am bringing inIf the conflict looks very large for the size of the change, that is a signal: you are probably porting a commit that depends on another one you have not ported. Look at git log --oneline <source> around the commit and check whether there is an earlier commit that is needed too.
When the commit conflicts because its change has already been applied on the destination branch, Git tells you in a different way:
The previous cherry-pick is now empty, possibly due to conflict resolution.
If you wish to commit it anyway, use:
git commit --allow-emptyThere, git cherry-pick --skip is the right answer: there is nothing to contribute.
- Rescuing a commit from an abandoned branch
The second legitimate case. Carla started feature/colour-labels and the team decided to shelve it: the approach was not convincing. But inside it there was one commit that was worth keeping: a function that validates the hexadecimal format of a colour and turns out to be useful for something else.
92c7a06 Show the colour label in the list d5e8f31 Add the colour field to the task model 5f1c4b8 Add hexadecimal colour validation e91d4a8 Extract the task element creation into its own function
Here Carla has not used -x, and quite right too: the source branch is going to be deleted, so leaving a reference in the message to a hash that will be left orphaned helps nobody. In cases like this, if you want to leave a record, write it in human language with -e:
Add hexadecimal colour validation Rescued from the feature/colour-labels branch, which was dropped for other reasons. The function is useful in its own right.
A third case, to complete the catalogue of legitimate uses: you were on the wrong branch. You have made three commits on main that should have gone on a feature branch. The classic solution is to create the branch where you are, and put main back where it belongs; but if you would rather not touch main, cherry-picking the three commits onto the right branch is a perfectly valid way out. The full range of "I committed on the wrong branch" cases is in lesson 09-01.
- The problem of duplicates
This is where cherry-picking makes you pay, and it is worth understanding properly because it is the main reason not to overuse it.
The scenario: Bruno has feature/weekly-report with five commits. One of them, 4d7c1a9, fixes a bug that is urgently needed in production now. Bruno cherry-picks that commit onto main:
Perfect: the bug is fixed on main and deployed. But now, in the repository, the same change exists twice: as 4d7c1a9 on Bruno's branch and as 2e9b6f3 on main.
gitGraph commit id: "e91d4a8" branch feature/weekly-report commit id: "a1c5e83" commit id: "4d7c1a9" commit id: "6f2b9d4" checkout main commit id: "2e9b6f3"
A fortnight later, Bruno finishes the feature and merges his branch into main. What happens?
The good case. If nobody has touched those lines since, the three-way merge notices that the change from 4d7c1a9 is already applied on main (the result is the same on both sides) and the merge comes out clean. Commit 4d7c1a9 enters the history, but its content is not applied twice. The result is correct; it is simply that main's git log shows two commits saying the same thing.
The bad case. If somebody has modified those lines on main after the cherry-pick, the merge sees a change on theirs (the one from 4d7c1a9) over a base that no longer matches, and it conflicts. It is the most bewildering conflict there is: the markers show two almost identical versions of code you have already fixed, and there is no way of understanding why it clashes unless you remember the cherry-pick from a fortnight ago.
The worst case. If the change is not idempotent — adding a line to a list, incrementing a counter, adding an entry to a configuration file — the merge may apply it twice without conflicting. The result builds, passes superficial tests and is wrong.
The conclusion is the one that gives the last section its title: cherry-pick is for when you are not going to merge afterwards, or for when merging is not an option (branches that never come together, such as main and maintenance/1.x).
- Detecting duplicates:
git cherry and --cherry-mark
git cherry and --cherry-markGit has tools for recognising commits that introduce the same change with different hashes. They are based on the patch-id: a hash computed over the content of the diff, ignoring the context, the blank lines and the line numbers. Two commits with the same patch-id do the same thing even though they are different objects.
git cherry compares two branches and marks each commit with + (it is not on the other one) or - (it is already there, with a different hash):
+ a1c5e83f2b7d9c4e1a8f6b3d5c2e9a7f4b1d8c6e Add the weekly report view - 4d7c1a9e8b2f5d3c7a9e1f4b6d8c2a5e3f7b9d1c Fix the calculation of overdue tasks + 6f2b9d4c1e7a3f8b5d2c9e6a4f1b8d3c7e5a2f9b Add the filter by week
The - on the second line means: "this commit is already applied on main, even though it has a different hash there". Exactly what we were looking for. The syntax is git cherry [-v] <upstream> [<head>], and -v adds each commit's subject.
It is especially useful before merging or before porting a batch of commits to maintenance: it tells you what really remains to be taken over.
git log --cherry-mark does the same within a log, over a three-dot range:
> a1c5e83 Add the weekly report view = 4d7c1a9 Fix the calculation of overdue tasks = 2e9b6f3 Fix the calculation of overdue tasks > 6f2b9d4 Add the filter by week
| Mark | Meaning |
|---|---|
< |
Only on the left-hand side (main) |
> |
Only on the right-hand side (the branch) |
= |
Present on both sides as an equivalent change (same patch-id) |
There they are, side by side, the two commits that do the same thing. Related variants:
# Hide the equivalent ones: only what really remains to be integrated
git log --oneline --cherry-pick --left-right main...feature/weekly-report
# A shortcut for the above, right-hand side only
git log --oneline --cherry feature/weekly-report...main--cherry-pick is the one you will use most: it filters out the noise and leaves you only the work that genuinely remains.
An honest limitation: the patch-id compares the diff. If there was a conflict during the cherry-pick and you resolved it differently, the resulting diff will not be identical and Git will not recognise it as equivalent. The detection is a help, not a guarantee.
- When NOT to use cherry-pick
| Situation | What to do instead |
|---|---|
| You want to take a whole branch over to another | git merge (or rebase if it is not published yet) |
| You are going to merge that branch later on | Wait and merge: you avoid the duplicates of section 7 |
| You need more than three or four consecutive commits | rebase --onto: it reapplies a whole stretch and leaves no duplicates |
You want to bring your branch up to date with main |
git merge main or git rebase main, never individual cherry-picks from main |
| The commit depends on others you are not taking over | Port the dependencies too, or redo the change by hand |
| You want to "copy" a commit onto the same branch | You are almost certainly after revert (05-06) or an interactive rebase |
The decision rule, in one sentence: if the two branches are going to end up joining, do not copy commits between them; merge them. Cherry-pick is for branches that live in parallel permanently (maintenance, old versions, separate environments) or for rescuing something from a branch that is about to die.
On the golden rule: of the three operations in this block, cherry-pick is the least dangerous, because it rewrites nothing. It adds a new commit to the branch you are on; it does not touch the source and does not force anybody to force a push. Its danger is not rewriting but duplication, which you pay for later and in the form of conflicts that are hard to explain.
Common Mistakes and Tips
Mistake 1: believing that cherry-pick "moves" the commit. It copies it. The original is still on its branch, as alive as before. If you wanted to move it, you have to delete it from the source with an interactive rebase (drop), with all that the golden rule implies.
Mistake 2: cherry-picking commits from a branch you are going to merge later. It is the number-one source of incomprehensible conflicts. If the branch is going in whole, wait.
Mistake 3: forgetting -x when porting to maintenance. In six months' time, that commit of unknown origin on maintenance/1.x will be a mystery. It costs two characters.
Mistake 4: using -x when copying from a local branch you are going to delete. The reference is left pointing at an unresolvable hash. There it is better to use -e and a note in human language.
Mistake 5: cherry-picking without looking at the commit first. git show <sha> takes three seconds and saves you discovering halfway through the conflict that the commit touched six files and depended on three others.
Mistake 6: not checking the range. A..B excludes A. If you expected five commits to be applied and four are, that is why. Always check first with git log --oneline A..B.
Mistake 7: getting the direction of git cherry the wrong way round. The first argument is the upstream (what you are comparing against, usually main) and the second is the branch you are examining. The other way round will give you precisely the opposite of what you expect.
Tip 1: make small, self-contained commits. That is not advice about cherry-picking, it is the advice that makes cherry-picking possible. A commit that does one single thing transplants without drama; one that mixes three does not.
Tip 2: always verify after porting. The patch applying without conflict does not mean it works on the other branch. Run the tests.
Tip 3: use git cherry -v before a porting campaign. It will tell you exactly what remains to be taken over and what is already there, without relying on your memory.
Tip 4: for large batches, git rebase --onto instead of many cherry-picks. It is the same operation underneath, but in one go and with a clear list of what is being reapplied.
Tip 5: if the cherry-pick gets complicated, abort. git cherry-pick --abort leaves the repository exactly as it was. Rebuilding the change by hand on the destination branch is sometimes quicker and always clearer than wrestling with a patch that does not fit.
Exercises
Exercise 1: porting a fix to maintenance
Set up a repository with main and a maintenance/1.x branch coming off an old commit. Add two commits to main, one of them a small fix. Then:
- Port only the fix to
maintenance/1.x, recording where it came from. - Demonstrate that the hash is different but the diff is identical.
- Demonstrate that the original author has been preserved.
Exercise 2: the duplicate and how to detect it
Deliberately provoke the problem of section 7:
- Create a branch with three commits.
- Cherry-pick the second one onto
main. - Use
git cherry -vto demonstrate that Git knows that change is already onmain. - Use
git log --cherry-mark --left-rightto see the two equivalent commits marked with=. - Merge the branch into
mainand observe what happens to the history.
Exercise 3: grouping several commits into one
With a branch that has four small commits, take the first three over to another branch as one single commit with a message of its own, using -n. Verify with git show --stat that the resulting commit contains the changes from all three.
Solutions
Solution 1:
mkdir /tmp/practice-cherry && cd /tmp/practice-cherry
git init -b main
echo "v1" > app.js && git add . && git commit -m "Initial version"
git switch -c maintenance/1.x
git switch main
printf 'v1\nnew feature\n' > app.js && git commit -am "Add the version 2 feature"
printf 'v1\nnew feature\nfix\n' > app.js
git -c user.name="Ana Ferrer" -c user.email="[email protected]" commit -am "Fix the focus after deleting"
git log --oneline(It conflicts because the feature line is missing on maintenance/1.x. It is resolved by keeping only the fix.)
# 2 and 3. Compare
git log --oneline -1
git show --pretty='%h | %an | %ad' --date=short HEAD | head -4
git show --pretty='%h | %an | %ad' --date=short 5c9e1f4 | head -4A different hash, the same authorship and the same author date. The new one's message additionally includes the line (cherry picked from commit 5c9e1f4...).
Solution 2:
mkdir /tmp/practice-duplicates && cd /tmp/practice-duplicates
git init -b main
printf 'line 1\n' > f.txt && git add . && git commit -m "Base"
git switch -c feature/something
printf 'line 1\nA\n' > f.txt && git commit -am "Commit A"
printf 'line 1\nA\nB\n' > f.txt && git commit -am "Commit B (the urgent fix)"
printf 'line 1\nA\nB\nC\n' > f.txt && git commit -am "Commit C"
git log --onelineOnly if the resulting diff matches; if your conflict resolution changed the patch, it will appear with +. That is the limitation we mentioned.
< 7b3e9f1 Commit B (the urgent fix) > c17b5f9 Commit A > 4d8e3b6 Commit B (the urgent fix) > 9f2a7c1 Commit C
You will see the ported commit and the original coexisting in main's history, saying the same thing twice. If the conflict was resolved differently, it will also have conflicted on merging. That is exactly the price of a premature cherry-pick.
Solution 3:
mkdir /tmp/practice-group && cd /tmp/practice-group
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"
git switch -c source
echo "one" > one.txt && git add . && git commit -m "One"
echo "two" > two.txt && git add . && git commit -m "Two"
echo "three" > three.txt && git add . && git commit -m "Three"
echo "four" > four.txt && git add . && git commit -m "Four"
git log --oneline sourcecommit 6e2d8b5...
Port the first three steps as a single change
one.txt | 1 +
three.txt | 1 +
two.txt | 1 +
3 files changed, 3 insertions(+)Note the range: a27c9f3^..3b9d6a2 includes One, Two and Three. Without the ^ you would have ported only Two and Three.
Conclusion
git cherry-pick is the module's precision tool. The essentials:
- It copies a commit's change onto your current branch, creating a new commit with a different hash. It preserves the message, the author and the author date; you become the committer.
- It accepts individual commits and ranges, with the usual trap that
A..BexcludesAandA^..Bdoes not. Merge commits are left out of ranges. -xrecords the origin and is indispensable when porting to public maintenance branches;-nlets you accumulate several commits and commit them as one;-eedits the message and-sadds the sign-off.- Conflicts are resolved as always (lesson 03-05) and you get out with
--continue,--skipor--abort. Hereoursandtheirsare NOT inverted: the inversion was a rebase thing. - There are two legitimate cases: taking a fix over to a branch that will never be merged with the source (maintenance, old versions), and rescuing a commit from a branch that is about to be abandoned.
- The price of overuse is duplicates: if you copy a commit from a branch you later merge, the same change ends up in the history twice and, in the worst case, applied twice.
git cherry -vandgit log --cherry-mark/--cherry-pickdetect equivalent changes by comparing the patch-id, and are the way to know what genuinely remains to be ported.- If the two branches are going to come together, do not copy: merge.
What comes next
The three previous lessons have manipulated commits: reapplying them, reorganising them, copying them. All of them start from the same premise: that the work is already committed.
But day-to-day life throws up a different and very frequent problem. You are halfway through something, with the file half written and nothing worth committing, and an emergency comes in that has to be dealt with now. You do not want to commit a wip (even though it can be fixed with what you learned in 05-02), you do not want to lose what you have done, and you need a clean working tree in order to switch branch.
Git has a drawer for that, and we have been promising it since lesson 03-02: git stash. We open it in lesson 05-04: Stashing Changes.
Mastering Git: From Beginner to Advanced
Module 1: Introduction to Git
- What Is Git?
- Installing Git
- Basic Git Terminology
- The Git Data Model
- Configuring Git
- Initial Configuration
Module 2: Basic Git Operations
- Creating a Repository
- Cloning a Repository
- The Basic Git Workflow
- Staging and Committing Changes
- Inspecting Changes with git diff
- Viewing Commit History
Module 3: Branching and Merging
- Understanding Branches
- Creating and Switching Branches
- Merging Branches
- Merge Strategies
- Resolving Merge Conflicts
- Branch Management
Module 4: Working with Remote Repositories
- Understanding Remote Repositories
- Adding a Remote Repository
- Authenticating with Remote Repositories
- Fetching and Pulling Changes
- Pushing Changes
- Tracking Branches
Module 5: Advanced Git Operations
Module 6: Git Tools and Techniques
- Using Git Hooks
- Git Bisect
- Git Blame
- Git Log and Aliases
- Git Submodules
- Multiple Working Copies with git worktree
Module 7: Collaboration and Workflow Strategies
- Forks and Pull Requests
- Code Reviews with Git
- The Git Flow Workflow
- GitHub Flow
- Trunk Based Development
- Continuous Integration with Git
Module 8: Git Best Practices and Tips
- Writing Good Commit Messages
- Keeping a Clean History
- Ignoring Files with .gitignore
- File Attributes with .gitattributes
- Security Best Practices
- Performance Tips
Module 9: Troubleshooting and Debugging
- Common Git Problems
- Undoing Changes
- Resolving Divergence with the Remote
- Recovering Lost Commits
- Dealing with Corrupted Repositories
- Advanced Debugging Techniques
