Git cherry pick vs rebase

Since the time git cherry-pick learned to be able to apply multiple commits, the distinction indeed became somewhat moot, but this is something to be called convergent evolution 😉

The true distinction lies in original intent to create both tools:

  • git rebase‘s task is to forward-port a series of changes a developer has in their private repository, created against version X of some upstream branch, to version Y of that same branch (Y > X). This effectively changes the base of that series of commits, hence “rebasing”.

    (It also allows the developer to transplant a series of commits onto any arbitrary commit, but this is of less obvious use.)

  • git cherry-pick is for bringing an interesting commit from one line of development to another. A classic example is backporting a security fix made on an unstable development branch to a stable (maintenance) branch, where a merge makes no sense, as it would bring a whole lot of unwanted changes.

    Since its first appearance, git cherry-pick has been able to pick several commits at once, one-by-one.

Hence, possibly the most striking difference between these two commands is how they treat the branch they work on: git cherry-pick usually brings a commit from somewhere else and applies it on top of your current branch, recording a new commit, while git rebase takes your current branch and rewrites a series of its own tip commits in one way or another. Yes, this is a heavily dumbed down description of what git rebase can do, but it’s intentional, to try to make the general idea sink in.

Update to further explain an example of using git rebase being discussed.

Given this situation,
a state of the repo before rebasing
The Book states:

However, there is another way: you can take the patch of the change that was introduced in C3 and reapply it on top of C4. In Git, this is called rebasing. With the rebase command, you can take all the changes that were committed on one branch and apply them onto another one.

In this example, you’d run the following:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

“The catch” here is that in this example, the “experiment” branch (the subject for rebasing) was originally forked off the “master” branch, and hence it shares commits C0 through C2 with it — effectively, “experiment” is “master” up to, and including, C2 plus commit C3 on top of it. (This is the simplest possible case; of course, “experiment” could contain several dozens of commits on top of its original base.)

Now git rebase is told to rebase “experiment” onto the current tip of “master”, and git rebase goes like this:

  1. Runs git merge-base to see what’s the last commit shared by both “experiment” and “master” (what’s the point of diversion, in other words). This is C2.
  2. Saves away all the commits made since the diversion point; in our toy example, it’s just C3.
  3. Rewinds the HEAD (which points to the tip commit of “experiment” before the operation starts to run) to point to the tip of “master” — we’re rebasing onto it.
  4. Tries to apply each of the saved commits (as if with git apply) in order. In our toy example it’s just one commit, C3. Let’s say its application will produce a commit C3′.
  5. If all went well, the “experiment” reference is updated to point to the commit resulted from applying the last saved commit (C3′ in our case).

Now back to your question. As you can see, here technically git rebase indeed transplants a series of commits from “experiment” to the tip of “master”, so you can rightfully tell there indeed is “another branch” in the process. But the gist is that the tip commit from “experiment” ended up being the new tip commit in “experiment”, it just changed its base:
state after merging

Again, technically you can tell that git rebase here incorporated certain commits from “master”, and this is absolutely correct.

Leave a Comment