8

Is this possible ? Cherry picking from other branch into specific commit of master branch. For eg: Say, I've two branch:

 -> master : <commit1>, <commit2>, <commit3>
 -> test: <c1>, <c2>, <c3>, <c4>

And now I want to cherry pick a commit (eg: <c3>) from test branch to a <commit2> in master branch.

Edit: I know it can be done via rebase command, but can it be done via cherry-pick only?

Ashish Rawat
  • 3,363
  • 5
  • 29
  • 35
  • Do you mean you want to add the changes from a specific commit in another branch to an existing commit in the master branch? – Michael Mior Apr 17 '17 at 09:51
  • yes, but via cherry-pick if possible because I heard cherry-pick is low level tool of rebase command. – Ashish Rawat Apr 17 '17 at 09:52
  • Why are you against using rebase? It's the correct tool for the job. – Michael Mior Apr 17 '17 at 10:09
  • I'm currently learning git, so right now I just want to keep my toolset small. That's why I'm avoiding rebase as it has more options. – Ashish Rawat Apr 17 '17 at 10:13
  • I understand that the original question refers to perform cherry-pick, nevertheless I found this answer on a similar question very good, at least to have an alternative perspective of how this situation could (should ?) be handled/resolved: https://stackoverflow.com/a/62402568/1971003 – Guy Avraham Aug 29 '20 at 14:38

4 Answers4

30

You have a fundamental error in how you are thinking Git works.

Is this possible ? Cherry picking from other branch into specific commit of master branch.

No: Git cannot change any commit. You can add new commits, and you can copy an existing commit to make a new commit from it, i.e., add a new commit that is a copy of an existing commit (with, presumably, at least one thing different in the copy). The latter is what git cherry-pick does.

You can also change the specific commit hash ID any branch name remembers for you.

Drawing branches

Say, I've two branch:

-> master : <commit1>, <commit2>, <commit3>
-> test: <c1>, <c2>, <c3>, <c4>

This is not a good way to draw branches. Here is a better way:

...--D--E--F--G   <-- master
      \
       H--I--J--K   <-- test

Commit E here corresponds to your <commit1>, I just used single letters to keep them shorter. Similarly, F is like your <commit2>, and J is like your <c3>. I also included another earlier commit, D, from which both the branches extend, to suggest that there are probably earlier commits before both E and H.

Note that each commit remembers its parent commit: the parent of G is F, so we say that G "points back" to F. F points back to E, which points back to D, and so on. Likewise, commit K points back to J which points back to I which points back to H. Note that H, which is only on branch test, also points back to D.

(Commit D is particularly interesting: it is on both branches. It is also what Git calls the merge base of the two branches. But that's not our concern here; it's just an interesting side note, that in Git, commits can be on more than one branch simultaneously.)

The branch names, in this case master and test, each point to one specific commit. Here the name master points to commit G, and the name test points to commit K. This is how we decide which commits are on which branches: we start, as Git does, using the branch names to find the branch tip commits. From those tip commits, we—or Git—work backwards: we go from any one commit, to its parent commit, and then to that commit's parent, and so on. This process stops only when we get tired of following parents (e.g., quit out of git log), or are told to stop—we won't see that here though—or we hit a root commit, which is a commit with no parents. The very first commit you ever make in a repository is a root commit, since there is no earlier commit it could use as a parent. (It's possible to make extra root commits, but again we won't see that here.)

Cherry-picking commits

And now I want to cherry pick a commit (eg: <c3>) from test branch to a <commit2> in master branch.

You can cherry-pick a commit, but the part I put in bold italics ("to a commit") is nonsense. Cherry-picking means copying a commit. The copy is a new commit, which you would normally add on to a branch.

Since your <c3> is my J, let's see what happens if you use:

git checkout master
git cherry-pick test~1

The first step gets you "on" branch master, i.e., new commits you add now will be added to master, by making the name master point to them.

The second step identifies commit J by walking back one step from the tip of test. The name test identifies commit K: a branch name "means" the tip commit of that branch. The ~1 suffix means "count back one parent", so we move from commit K to commit J. That is the commit we ask Git to copy.

We'll ignore the exact mechanics of doing this copy, for the moment, and just look at the result. Git makes a new commit that is "like" J; let's call this new commit J'. There are several differences between the original J and the new copy J', and the important one for the moment is that the parent of the copy is the existing tip of master, i.e., commit G. So J' points back to G.

Once the copy is done, and the new J' is stored permanently, Git updates master to point to the new commit we just made:

...--D--E--F--G--J'  <-- master
      \
       H--I--J--K   <-- test

Note that no existing commit is affected. This is because it's literally impossible to change any existing commit.

Rebase

I know it can be done via rebase command ...

It can't and this really does matter. What rebase does is not modify commits. Instead, it makes copies—possibly a whole lot of copies.

Let's consider the case where you have the D-E-F-G sequence on master and you'd like to copy J and insert it after F but before G. That is, you would like to get this, even though it's impossible:

...--D--E--F--J'-G   <-- master
      \
       H--I--J--K   <-- test

The reason it's impossible is that G cannot be changed, and G already points back to F. But what if we did this instead:

             J'-G'  <-- new-master
            /
...--D--E--F--G   <-- old-master
      \
       H--I--J--K   <-- test

In other words, what if we copy J to J' on a new new-master branch, with J' coming after F, and then copy G to G', with G' coming after J'? We can do this with git cherry-pick, we just have to cherry pick two commits, J first, and then G, onto a new branch:

git checkout -b new-master master~1    # make the new-master branch at F
git cherry-pick test~1                 # copy J to J'
git cherry-pick master                 # copy G to G'

And, suppose once we have done that, we erase the old master name entirely and just call the new one master, like this:

             J'-G'  <-- master
            /
...--D--E--F--G   [abandoned]
      \
       H--I--J--K   <-- test

Now if we stop drawing the abandoned original G, and draw master in a straight line, it looks like we've inserted J' between F and G ... but we have not: we instead copied G to G'.

The git rebase command is, essentially, an automated, high-powered "cherry pick a lot of commits" command, followed by some branch name moving-about at the end.

Why all this matters

This only matters sometimes, but when it does matter, it matters a lot.

Let's say we start with this, which is a lot like before except there's another branch too:

...--D--E--F--G   <-- master
      \        \
       \        L   <-- develop
        \
         H--I--J--K   <-- test

Now let's copy J to J', putting that after F, and then copy G to G' with J' as G''s parent, and make the name master remember the hash ID for new commit G'. (This is the same rebase-with-copy we did before; we just have this extra develop branch.)

             J'-G'  <-- master
            /
...--D--E--F--G
      \        \
       \        L   <-- develop
        \
         H--I--J--K   <-- test

Now let's re-draw this so that there's less weirdness in the layout:

...--D--E--F--J'-G'  <-- master
      \     \
       \     G--L   <-- develop
        \
         H--I--J--K   <-- test

Even though we got Git to "forget" the original commit G on master in favor of the shiny new G' that has J' as its parent, the original G still there on branch develop. In fact, now it looks as though G was always on develop and we probably cherry-picked it into master from there.

The key point here is that you can copy commits and forget that the originals were there, but if so, you must be sure you really forget them. If those originals are known elsewhere—through another branch in your own repository, or (worse) git pushed to another repository—you have to get all the other users to use the new versions, if that's what you want. You cannot change the originals, you can only copy them to new ones and get every user to switch to the new ones.

torek
  • 448,244
  • 59
  • 642
  • 775
  • 1
    Upvoted, not only because you've detailed answer. But you also actually explained the reason why I'm thinking incorrectly. – Ashish Rawat Apr 17 '17 at 13:05
2

Yes, you can use git cherry-pick <commit from test branch> to apply changes to master branch.

To cherry pick a commit from test branch to a commit (not the latest) on master branch, you can use below way:

git checkout <a commit on master you want to cherry pick>
git cherry-pick <a commit from test branch>
git rebase --onto HEAD <the commit you used in step1> master
Marina Liu
  • 36,876
  • 5
  • 61
  • 74
  • I don't want to apply changes to head of the master branch. I just want to apply to specific commit in master branch via cherry-pick – Ashish Rawat Apr 17 '17 at 09:46
  • You can checkout to the commit firstly,and then cherry-pick a comment,then use git rebase --onto HEAD master – Marina Liu Apr 17 '17 at 09:54
  • `git rebase --onto HEAD` is probably not needed if you did `git checkout my-branch` in the first step. In fact, if you do `git rebase --onto HEAD my-branch`, then it does nothing. – Nawaz Jun 09 '22 at 15:47
1

You are going to re-write your repository history, which is not something that is recommended.

  • First, create a new branch at the necessary commit in master branch.
  • Checkout new branch.
  • Cherry pick the necessary commit from test branch into new branch.
  • Checkout master branch.
  • Rebase master branch into new branch.

You will have to force push master branch if you need to push the master branch to a remote repository. If others have checked in the master branch, their master branch will get invalidated by doing this.

Lahiru Chandima
  • 22,324
  • 22
  • 103
  • 179
  • The point is I don't want to use rebase, just cherry-pick. If I want to do that, then your 2 point is not necessary. – Ashish Rawat Apr 17 '17 at 10:01
  • @AshishRawat: unfortunately, you *must* use a temporary branch ("checkout the new branch", step 2) because Git only adds new commits to branches. You can use what Git calls a "detached HEAD" to do this—that's how `git rebase` does it—to avoid needing a temporary branch *name*, but you still have a temporary branch. – torek Apr 17 '17 at 11:50
0

If you want to change an existing commit, you can use rebase in combination with cherry-pick.

GIT_SEQUENCE_EDITOR='sed -i s/^pick/edit/' git rebase -i <commit2>
git cherry-pick -n <c3>
git commit --amend -C HEAD
git rebase --continue

This first runs rebase to select the commit on master for editing. Then it applies the changes from the other commit, modifies the commit on master, and finishes the rebase. Of course this doesn't actually change the commit, but it will create a new commit that has the changes from both <commit2> and <c3>.

The use of GIT_SEQUENCE_EDITOR just avoids having to manually select the single commit for editing when starting the rebase.

Michael Mior
  • 28,107
  • 9
  • 89
  • 113