Cherry-pick: copy one commit
The interactive lesson needs JavaScript. The text below is the complete lesson.
After this lesson you can
- copy the change from one specific commit onto another branch with
git cherry-pickapply - explain why the copy gets a new ID even though the change is the same, and what
-xadds understand - resolve or abort a cherry-pick that conflicts apply
- use cherry-pick for a single commit and merge for a whole branch apply
Understand: One fix, not the whole branch
Version 1.0 is out with users and lives on the branch release-1.0. On main the team has kept building new features. In the middle of them sits a fix, D, that the users of 1.0 need now.
Merging main into release-1.0 would bring everything, including the dashboard and the charts. They are not ready for a release. You want exactly one change.
You stand on release-1.0 and run git cherry-pick. It looks at what commit D changed compared with its parent, and makes the same change here. The result is a new commit, drawn as D prime.
The copy has the same message and the same change, but a new ID. It has a different parent and a new time, so it is a different commit. The original D stays untouched on main.
With the option -x, Git writes a line at the end of the message saying which commit the copy came from. That leaves a trail you can follow later. Use it when the original is already shared with others.
If the change does not fit where you stand, you get a conflict, just like a merge. Fix the file, run git add, and finish with git cherry-pick --continue. If you would rather go back to where you were, use git cherry-pick --abort.
Cherry-pick is for single commits, like an urgent fix for a release. When a whole branch should come in, use merge. Many copies create duplicates with different IDs, and they can cause confusing conflicts when the branches meet later.
Watch: An urgent fix for the release branch
First we look at both branches. release-1.0 is at Add login. On main the fix is second from the top, between the dashboard and the charts.
$ git log --oneline --graph --all
The copy should land on the release branch, so we stand there when we copy.
$ git switch release-1.0
Now we copy the fix. main~1 is the commit just below the tip of main, which is Fix login timeout. The option -x notes where the copy came from. What does release-1.0 get?
$ git cherry-pick -x main~1
Compare the two commits called Fix login timeout. The subject line is the same, but the IDs differ. They are two different commits with the same change.
$ git log --oneline --graph --all
The full message shows the line that -x added. It points back to the full ID of the original on main.
$ git log -1
Finally we check which files the branch tracks. Only the README and login.txt. The dashboard from main did not come along, just as we wanted.
$ git ls-files
Tasks
- Show the history of all branches as a graph, one line per commit.
- Go to the branch that should get the fix.
- Copy the fix Fix login timeout onto
release-1.0, and let Git note where it came from. - Show the full message of the newest commit on your branch.
Practise: Transfer: pick from an experiment
You are on main. The branch experiment has three commits: Change greeting, Add input validation and Try dark theme. Only some of them should come in.
Tasks
mainneeds only the commit Add input validation fromexperiment. Copy it tomainwithout the other experiments.- Also copy Change greeting from
experiment. It clashes with the README onmain. Resolve the conflict so the README has two lines, first Hello and then Welcome to main, and finish the copy.
Practise: Transfer: abort a copy
You are on main, in the middle of a cherry-pick of Change greeting from experiment, and it conflicted in the README. No demo this time.
Tasks
- You decide the README on
mainshould stay as it is. Cancel the copy, so everything is exactly as it was before it started.
Check yourself: Recall it
You cherry-pick D onto release-1.0. The copy D′ has the same change as D, but a different ID. Why?
- Because
-xadded a line to the message - Because D′ has a different parent, and the parent is part of what the ID is computed from
- Because Git gives every copy a random new ID
You cherry-pick commit D. What comes along?
- D and every commit before it
- Only the change D made compared with its parent
- D's whole snapshot, replacing your files
A feature branch with twelve commits is done and should go into main. What fits best?
- Cherry-pick all twelve, one by one
- Merge the branch
- Cherry-pick only the last one, since it contains everything
Review from “Rebase”: a rebase is in effect a series of cherry-picks. What does that mean for the commits a rebase makes?
- They are the same commits, just moved to a new place
- They keep their IDs as long as there is no conflict
- They are new commits with new IDs, so you rebase only commits nobody else has
Common misconceptions
- “Cherry-pick moves the commit.” The original stays where it is. You get a new commit with the same change and a new ID.
- “Cherry-pick brings everything up to that commit.” It copies only what that one commit changed, not its ancestors.
- “Cherry-pick is a fine way to move a whole branch.” Many copies create duplicates that easily conflict later. Use merge for a whole branch.
Read more
What cherry-pick computes
git cherry-pick C takes the difference between commit C and its parent and applies that difference to the commit you stand on, as a three-way merge. If it applies cleanly, Git makes a new commit with C's message and author, but with your HEAD as parent and a new committer date. New parent and new date mean a new ID: the original and the copy are two different commits that happen to contain the same change.
That has two consequences. A cherry-pick never brings the commits before C, so if C depends on something introduced earlier, the copy may conflict or not even work. And Git does not record a link between original and copy unless you ask for one.
Several commits at once
You can name several commits, or a range. A range A..B means "the commits reachable from B but not from A", so A itself is not included. The commits are applied oldest first.
git cherry-pick 3a24001 d847db3
git cherry-pick main~3..main~1
git cherry-pick main~3^..main~1
The second line copies main~2 and main~1; the third also includes main~3. Use git log --oneline main~3..main~1 first to see exactly what a range contains.
When it stops: continue, skip, abort, quit
A cherry-pick of several commits is a small sequence. When one step conflicts, Git stops and tells you what you can do:
| Command | Effect |
|---|---|
git cherry-pick --continue |
after you have fixed the files and run git add, commit and go on |
git cherry-pick --skip |
drop this commit and go on with the next |
git cherry-pick --abort |
stop and return to exactly where you were before you started |
git cherry-pick --quit |
stop, but keep the commits already made |
A step can also stop because the change is already there. The examples from here on start from the lesson's repository after the demo, but with one more commit on main, Add login retries, between the fix and Add charts. Then main~2 is the fix and main~1 the new commit. The fix was already picked in the demo, so copying it again would change nothing:
$ git cherry-pick main~3..main~1
The previous cherry-pick is now empty, possibly due to conflict resolution.
...
Otherwise, please use 'git cherry-pick --skip'
$ git cherry-pick --skip
[release-1.0 0c100af] Add login retries
git status tells you when a cherry-pick is in progress. While .git/CHERRY_PICK_HEAD exists, do not start something new: finish, skip or abort first.
Leave a trail: -x and backports
This is the message of the demo's copy right after git cherry-pick -x main~1:
$ git log -1 --format=%B
Fix login timeout
(cherry picked from commit 3a2400125cf44e636d8c5f3bbf72332003118ab6)
The line from -x lets anyone find the original later. Use it when the original is already published, as in a backport: a fix made on main and copied to an older release branch. A common team convention is to fix on main first, then cherry-pick with -x to each supported release branch, so the fix never exists only on an old branch. Do not use -x for a private commit you are just moving around; the reference would point to something nobody else can see.
Two related options: -n (--no-commit) applies the change to the working tree and staging area without committing, which is handy for combining several picks into one commit. -e lets you edit the message.
Which commits are already copied?
Because the IDs differ, git log cannot tell that two commits are copies. Git can compare the changes themselves (their patch ID):
$ git cherry -v release-1.0 main
+ 4e3574ec0227522c30bffeb244837dd4aa75c372 Add dashboard
- 3a2400125cf44e636d8c5f3bbf72332003118ab6 Fix login timeout
- d847db3f9e6e7b0a1418475ecef595b2cdb0489b Add login retries
+ b3f3e101177d5334c4edfd45a2c59f8228550eb9 Add charts
A minus means an equivalent change already exists on release-1.0; a plus means it does not. git log --oneline --cherry-pick --right-only release-1.0...main lists the same plus commits as an ordinary log.
Cherry-pick, merge, rebase or revert?
- Merge when a whole branch should come in. The history shows that it came in, and later merges know what is already there.
- Cherry-pick for a few chosen commits, typically fixes for a release branch.
- Rebase when you want to move your own unpublished commits onto a new base. Rebase is in effect a series of cherry-picks plus moving the branch.
- Revert when a commit should be undone on a shared branch.
git revertmakes a new commit with the opposite change, instead of copying it.
Copying many commits between long-lived branches gives duplicates. When those branches are later merged, the same change arrives twice under different IDs. Git often resolves that silently, but if the code was changed in between, you get confusing conflicts.
Per-Arne's Git Good!