Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cherry-pick to specific commit from other branch

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?

like image 859
Ashish Rawat Avatar asked Apr 17 '17 09:04

Ashish Rawat


People also ask

Is it possible to cherry pick a commit from another git repository?

Can you cherry pick from another repository in Git? It is possible to cherry pick from another repo using the command line. You will first need to add the other repository as a remote and then fetch the changes. From there, you should be able to see the commit in your repo and cherry pick it.

How do you cherry pick a commit from another branch using TortoiseGit?

Cherry-picking in TortoiseGit is invoked from the Revision Log Dialog. Within this dialog, select the commit(s) to cherry-pick, then right-click on one of the selected commits to pop up the context menu. Select Cherry Pick this commit... (or Cherry Pick select commits... if more than one commit is selected).


2 Answers

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.

like image 168
torek Avatar answered Sep 21 '22 00:09

torek


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.

like image 28
Lahiru Chandima Avatar answered Sep 23 '22 00:09

Lahiru Chandima