Git cherry pick vs rebase

Viewed 77715

I have recently started working with Git.

Going over the Git book online I found the following under the "Git Rebase" section:

With the rebase command, you can take all the changes that were committed on one branch and replay them on another one.

(Quoted from: http://git-scm.com/book/en/Git-Branching-Rebasing)

I thought this is the exact definition of git cherry-pick (reapply a commit or a set of commit objects on the currently checked out branch).

What is the difference between the two ?

7 Answers

They're both commands for rewriting the commits of one branch on top of another: the difference is in which branch - "yours" (the currently checked out HEAD) or "theirs" (the branch passed as an argument to the command) - is the base for this rewrite.

git rebase takes a starting commit and replays your commits as coming after theirs (the starting commit).

git cherry-pick takes a set of commits and replays their commits as coming after yours (your HEAD).

In other words, the two commands are, in their core behavior (ignoring their divergent performance characteristics, calling conventions, and enhancement options), symmetrical: checking out branch bar and running git rebase foo sets the bar branch to the same history as checking out branch foo and running git cherry-pick ..bar would set foo to (the changes from foo, followed by the changes from bar).

Naming-wise, the difference between the two commands can be remembered in that each one describes what it does to the current branch: rebase makes the other head the new base for your changes, whereas cherry-pick picks changes from the other branch and puts them on top of your HEAD (like cherries on top of a sundae).

Both do very similar things; the main conceptual difference is (in simplified terms) that:

  • rebase moves commits from the current branch to another branch.

  • cherry-pick copies commits from another branch to the current branch.

Using diagrams similar to @Kenny Ho's answer:

Given this initial state:

A---B---C---D master
     \
      E---F---G topic

...and assuming that you want get the commits from the topic branch replayed on top of the current master branch, you have two options:

  1. Using rebase: You'd first go to topic by doing git checkout topic, and then move the branch by running git rebase master, producing:

    A---B---C---D master
                 \
                  E'---F'---G' topic
    

    Result: your current branch topic was rebased (moved) onto master.
    The topic branch was updated, while the master branch remained in place.

  2. Using cherry-pick: you'd first go to master by doing git checkout master, and then copy the branch by running git cherry-pick topic~3..topic (or, equivalently, git cherry-pick B..G), producing:

    A---B---C---D---E'---F'---G' master
         \
          E---F---G topic
    

    Result: the commits from topic were copied into master.
    The master branch was updated, while the topic branch remained in place.


Of course, here you had to explicitly tell cherry-pick to pick a sequence of commits, using the range notation foo..bar. If you had simply passed the branch name, as in git cherry-pick topic, it would have picked up only the commit at the tip of the branch, resulting in:

A---B---C---D---G' master
     \
      E---F---G topic

They are very different:

  • A rebase is a "magical" operation that modifies branches to update them.
  • A cherry-pick by contrast is a fine-grained operation which copies commits.

In fact, cherry picks can even be used to replace rebases.

The outdated branch problem:

An outdated branch is a daily scenario.

  1. You branched of from a "master" branch,
  2. Committed some changes,
  3. While in parallel also the initial "master" branch changed.

problem case

As illustrated above, there are several ways to depict this initial state:

  • Even though we do tend to show a kind of "branched" diagram when several branches have the same commits. In reality each branch has its own line of commits, some of which have the same hash code.
  • It's important to realize that there isn't such a thing as "master" or "child" relation between branches in git. All of that is just orchestrated by naming conventions.

On top of that, usually you would have 2 versions of most branches: a local and remote version. So, if we want to be as verbose as possible, we would show it as follows:

A--B--D      (remote) origin/master
     
A--B--C--E   (remote) origin/feature/foo
A--B--C--E   (local) feature/foo

The challenge now, is to update the history of our 2 feature branches, to add the missing changes of commit D. And there are in fact 2 ways to accomplish this.

Rebase solution

Rebase really is a magical command that updates a feature branch for you. It does so by adding the missing commits (i.e. commit D of the master) to a branch that "runs behind" a couple of commits. And it does so by adding the commits IN FRONT of the new commits.

rebase

After a rebase, all we need to do, is push the result, to update the remote branch with the updated version.

A--B--D         (remote) origin/master
     
A--B--D--C--E   (remote) origin/feature/foo
A--B--D--C--E   (local) feature/foo

Or as it would often be visualized.

look it moved

And that kind of gives the impression that our feature branch "moved", while in reality, it just got a bit longer.

Cherry-pick solution

You can in fact accomplish the same by combining a "hard reset" with multiple "cherry-picks". Let's start from the same problem, and perform a "manual rebase" by using cherry-picks.

cherry pick rebase

Which would have the same result as a rebase

A--B--D         master
       \
        C--E    feature/foo
Related