Lesson 3 of 5 in Rewriting History

Cherry-pick

Story: You don't need that whole timeline. Just one event from it, copied into yours.

Sometimes you need one commit from another branch, not the whole branch. A fix was made on a branch that isn't ready to merge. A useful commit is stuck in an experiment you're giving up. git cherry-pick copies the commits you name onto the branch you're on.

Copying a commit

First find the commit. git log --oneline redesign lists a branch's commits, newest first:

$ git log --oneline redesign
3e309cb (redesign) Try a wide layout
5d0b961 Fix a typo on the about page
303a5d5 Try an orange heading
...

Then, on the branch that needs it, name the commit:

$ git cherry-pick 5d0b961
[main 34434b6] Fix a typo on the about page
 Date: Mon Jan 1 09:18:00 2024 +0000
 1 file changed, 1 insertion(+), 1 deletion(-)

git applies that commit's change to your branch and commits it with the same message. The copy keeps the original's author and date, which is why git prints a Date: line.

You can name the commit by its id, as above, or relative to a branch, as you learned in Branching & Merging: redesign~1 is the commit just before redesign's newest. git cherry-pick redesign~1 does the same as git cherry-pick 5d0b961 here.

A copy, with a new id

Like a rebase, a cherry-pick makes a new commit: the same change and message, a new parent, and so a new id. The original stays on its branch, untouched. Now two commits in your repository hold the same change.

That's usually fine. If you later merge redesign into main, git sees that both sides made the same change, and merges it without a conflict. If you later rebase redesign onto main, git notices that main already has that change, and leaves your branch's copy out:

$ git rebase main
warning: skipped previously applied commit 5d0b961
hint: use --reapply-cherry-picks to include skipped commits
hint: Disable this message with "git config advice.skippedCherryPicks false"
Successfully rebased and updated refs/heads/redesign.

Several commits

git cherry-pick takes more than one commit. Name them oldest first: git copies them in the order you give, one new commit for each.

git cherry-pick main~2 main

In fact, a rebase is a series of cherry-picks: it copies each of your commits, in turn, onto the new base.

When a cherry-pick stops

A cherry-pick applies a change to a different starting point, so it can conflict, just like a merge or a rebase. git stops, marks the conflict in the file, and its hint names your choices: git cherry-pick --continue, --skip or --abort. This Terminal runs --continue and --abort.

Continue. Fix the file, stage it, and carry on:

git add style.css
git cherry-pick --continue

git opens the original's message in the edit pane. Save it as it is to keep it. git commits the copy, and the copy keeps the original's author. If you forget to stage the file, git refuses, as it does for a commit with a conflict in it. git commit finishes a cherry-pick of one commit too: git sees the cherry-pick in progress, and the edit pane opens with the original's message.

Abort. git cherry-pick --abort gives up. Your branch and your files go back to how they were before the cherry-pick, and the cherry-pick ends. Changes you hadn't committed in other files stay as they were. git reset --hard would end the cherry-pick too, but it throws those changes away.

And if the change is already on your branch, there's nothing to copy: git stops and says the cherry-pick is now empty. git cherry-pick --continue can't go on from there, because there's nothing to commit. git cherry-pick --abort gets you out. On your own computer, git cherry-pick --skip leaves that commit out instead.

When several commits stop partway

When you name several commits, git keeps a list of the ones still to copy. If it stops before the last one, the rest are waiting:

  • git cherry-pick --continue commits the copy git stopped on, as above, then copies the rest. If another one stops, do the same again.
  • git cherry-pick --abort gives up on the whole list. Your branch goes back to where it was before the cherry-pick, without the copies it had already made.

The other ways out of one commit don't end a list. git commit copies only the commit git stopped on, and the rest stay waiting until you run git cherry-pick --continue. git reset --hard puts your files back, but the cherry-pick stays in progress: git status still says Cherry-pick currently in progress., and git switch refuses to change branches.

If a commit in the list is already on your branch, git stops there with nothing to copy, and --continue can't get past it. Abort, then name the commits again without that one. So if a copy might already be on your branch, cherry-pick one commit at a time, oldest first.

Cherry-pick or merge?

Cherry-pick when you need one change now and not the rest of the branch. If you're going to merge the whole branch soon anyway, just merge it: copies of the same change on two branches make history harder to follow.

New commands

  • git cherry-pick
  • git cherry-pick --continue
  • git cherry-pick --abort

Explainer: Cherry-pick copies one commit

project: your repo, you are here37d7cf7 Add a README37d7cf7ab89288 Add the home pageab89288a4b8d97 Style the headinga4b8d97d724c80 Try an orange heading (unreachable)d724c801c218b9 Add a contact page (unreachable)1c218b96698940 Try a huge heading (unreachable)669894035acbc3 Add a contact page35acbc3HEAD → main
The repository at the end of the Explainer.
Graph as text

project: your repo, you are here

  • HEAD → main at 35acbc3 "Add a contact page"

Branches and tags

  • main at 35acbc3 "Add a contact page"

Commits, newest first

  1. 35acbc3 "Add a contact page"; parent a4b8d97; refs: HEAD → main
  2. a4b8d97 "Style the heading"; parent ab89288
  3. ab89288 "Add the home page"; parent 37d7cf7
  4. 37d7cf7 "Add a README"; no parent

Unreachable

  • 6698940 "Try a huge heading"; parent 1c218b9
  • 1c218b9 "Add a contact page"; parent d724c80
  • d724c80 "Try an orange heading"; parent a4b8d97

Challenge 1 of 3

Story: The redesign timeline isn't ready, but one of its corrections is. Copy it into the main one.

The branch redesign isn't ready to merge. But one of its commits, Fix a typo on the about page, fixes a typo that main has too, and main needs the fix now.

You're on main. Copy just that one commit onto main. Leave redesign as it is.

Unlocks after Remotes & Collaboration and Undoing Things.

Terminal

Unlocks after Remotes & Collaboration and Undoing Things.

This Lesson can be played once Remotes & Collaboration and Undoing Things are complete. Its text and Explainer are open to everyone.