86

Background info:

Due to restrictions in workflow with out existing systems, we need to set up a somewhat unorthodox git process.

(patch)    A-B---F
             |   |
(hotfix)     C-D-E
                 |
(dev)      1-2-3-G

On the patch branch, there are some commits. The files here are similar but not identical to the ones on dev (sync scripts switch around the order of settings in many of the files, making them appear changed while they are functionally the same).

A fix is needed on this branch so a hotfix branch is created and worked on. This branch is then merged back into patch, so far, so good.

This same fix needs to be deployed to the dev branch so it stays relatively in sync with patch, but trying to merge the hotfix branch leads to git trying to merge all the unrelated and 'unchanged' files from A and B as well, rather than only C,D and E.

Question:

It seems that cherry-pick does what we want in terms of only getting changes from selected commits, but I would really like a way to cherry-pick all commits in a given branch at once, without having to look up the commit ids every time.

Valyrion
  • 2,342
  • 9
  • 29
  • 60

12 Answers12

120

It seems that cherry-pick does what we want in terms of only getting changes from selected commits, but I would really like a way to cherry-pick all commits in a given branch at once, without having to look up the commit ids every time.


Using cherry-pick

git cherry-pick allows you to pick any commits you made in any branch to any other branch. In your case, you can simply checkout the master branch and then cherry-pick all the commits from any branch that you wish (cherry-pick supports ranges so you can specify start and end commits instead of listing all the commits).

This way you can control the way your commits will appear in the desired branch.

For example:

git cherry-pick ebe6942..905e279

enter image description here

# 1. Find the range of commits you wish to re-add to your branch.
# 2. Use git cherry-pick to add them back to the branch,
#    noting the range is exclusive of the start and inclusive of the end 
git cherry-pick start..end

# 3. If you wish to include the start commit as well,
#    add ^ right after start like so:
git cherry-pick start^..end

How to find the first commit of the branch?

git log

# Print out the latest commit (first one) of the given branch
git log  --oneline | tail -1

merge-base

Use the merge-base command to find where the branch was split from the original branch:

git merge-base A B

enter image description here

CodeWizard
  • 128,036
  • 21
  • 144
  • 167
  • 19
    Yes, but this still requires me to go through the log and find the relevant commit ids. I'm looking for something like `git cherry-pick hotfix` that would automatically find the commits in this branch and act like `git cherry-pick D^..E` without me having to provide the ids. – Valyrion Feb 16 '16 at 16:24
  • 1
    Answer updated with the commands how to find out the desired commits – CodeWizard Feb 16 '16 at 16:40
  • I was really hoping to get a one-liner. And if there's not a way to do it directly, a roundabout way would be something like: `git cherry-pick $( )^..$(some command that outputs the last commit hash of a branch )` unfortunately, this turns out to be pretty complicated – Kabir Sarin Sep 24 '19 at 11:00
  • 1
    The command `git log --oneline | tail -1` gives me the first commit of the entire repo. This is most certainly not what is wanted. Perhaps `git log --oneline -n 1` is a better. Also, the `git merge-base` command gives the commit in the base branch, before branch commits started. Maybe for that `git log A..B --oneline | tail -1` would be more correct. Otherwise, you're cherry picking something not from the branch. – AlanSE Oct 28 '19 at 16:47
  • 3
    `git log --oneline | head -1` gives the latest commit on ``. Or you could just do: `git cherry-pick $(git merge-base A B)..B` – levsa May 21 '21 at 12:01
34

If you want to cherry-pick all commits from branch dev.

Try:

git cherry-pick ..dev

Linji
  • 349
  • 3
  • 2
  • 3
    This apparently picks the entire history of the selected branch, including changes from before the creation of the branch. Thinking about it this is no surprise since for git branches are just pointers to the branch's current commit. This means git cannot find the first commit. – Brandlingo Mar 27 '19 at 15:06
  • 2
    As I know and test, this command will find the two branch's merge-base automatically. And only pick the commits after the merge-base. – Linji Apr 01 '19 at 09:07
  • Yes, sure. But if there is branch A, you branch from that one and create B. Now you want to get only the changes of B into C, but not those of A which are also contained in B. Your approach would bring them in as the base of A and C is before B. You would end up with the changed of A that you wanted to avoid in the first place. – Brandlingo Apr 02 '19 at 07:42
  • Yes you are right. In this case the merge-base needs to be calculated manually. – Linji Apr 04 '19 at 05:54
  • 1
    How is it possible that git is not able to do this for cherry-picks, when it can do it perfectly well and easily for a rebase? After all, (conceptually), a cherry-pick is just copying a branch and rebasing is just moving a branch. – cowlinator Feb 23 '21 at 04:41
  • this cherry picks all commits between HEAD..branch – CervEd Sep 22 '21 at 15:39
  • Nice! This doesn't work for the OP's situation, but it works great for me! I just want to apply the commits from `dev`, which has diverged from `master`, onto `master` without a merge commit. I could do this with a rebase, but that takes a couple of commands, so `cherry-pick` is faster and works great! – Joseph238 Oct 22 '21 at 16:32
18

A one liner would be:

git cherry-pick $(git merge-base master my/branch)..my/branch

You can also convert this into a git alias or bash function. I prefer using it like this so I only change single place:

BRANCH=my/branch; git cherry-pick $(git merge-base master ${BRANCH})..${BRANCH}
nimcap
  • 10,062
  • 15
  • 61
  • 69
  • 2
    Work correctly, and seems to me like the only solution here that doesn't put unreasonable demands on the user for such a simple request. On top of that, this is an extremely normal and common problem. If you attempt to contribute to a project and use the wrong base branch, you have to move it. This is what you need to do. You don't want to muck with commit differences between the two base branches, because you are not the maintainer of those. – AlanSE Nov 19 '21 at 17:25
11

Assuming you know the number of commits you wish to pick from the branch you can use the relative commit notation.

git cherry-pick BRANCH_A~10^..BRANCH_A

This will cherry pick all commits starting at 10 commits before (~10) BRANCH_A's HEAD, inclusive of the starting commit (^), and will get all commits in the range (..) through to BRANCH_A's HEAD.

Josh
  • 1,517
  • 1
  • 13
  • 21
  • 1
    This is not really what the question asked, but it's exactly what I want. Thanks! – cyqsimon Sep 07 '22 at 18:30
  • I like that idea, but I get a fatal error when I try it (fatal: bad revision), but when I tried to cherry pick each commit by the SHA, it worked. – Yandiro Nov 07 '22 at 03:11
3

Following from discussion in the accepted answer, here is a one-liner. It should not matter what branch you are currently on when you run this (presumably some non-default branch). This assumes that devel is the default branch, and that you want to cherry pick all commits from branch B, after it diverged from the devel branch.

git cherry-pick $(git log devel..B --pretty=format:"%h" | tail -1)^..$(git log B -n 1 --pretty=format:"%h")

The command syntax goes first..last, but we are using a variant here which is more like first-1^..last.

The command git log devel..B --pretty=format:"%h" | tail -1 gets the commit in the devel branch which is a parent of the first commit in the B branch. This is a little clunky, but the git merge-base command did not have the needed formatting options.

The command git log B -n 1 --pretty=format:"%h" just gets the last commit in that branch.

This will put your in an interactive cherry-pick context. So make sure to abort if it's not going well.

AlanSE
  • 2,597
  • 2
  • 29
  • 22
1

Use rebase

    A _ _ _ B
  /
- - - C

git rebase --onto=target start end

git rebase --onto=C A B

    A
  /
- - - C - - - B
CervEd
  • 3,306
  • 28
  • 25
1

I was disappointed that there wasn't really a great solution for this, so I made my own alias

aliasName = "!f() { git cherry-pick -n -Xsubtree $(str=$(git log $1 --grep 'Create branch' -n1 --oneline) && echo ${str:0:11})...$1 ; }; f"

So this looks like

git aliasName my/branch-name-to-cherry-pick

I'm fortunate in that we have a common name for all branches that are cut and they contain "Create branch" in the message. This alias runs a bash function that cherry picks the range from the first commit hash matching the grep found on the specified branch parameter ($1) through the head of the branch.

It's not perfect due to the name matching thing, but it works well in my case and maybe it will for someone else.

David
  • 95
  • 1
  • 7
0

checkout your branch(target branch) and do

git cherry-pick -m 1 hashCode

Alwin
  • 153
  • 1
  • 6
0

This should also work. We call the point of divergence between the HEAD (master in this case) and the branch and pass it to cherry-pick.

git merge-base origin/dev_single_doc_fit HEAD | xargs git cherry-pick {}..HEAD^
David Beauchemin
  • 231
  • 1
  • 2
  • 12
0

one more way with fast-forward.

The case:

you started newBranch, commited first commit and did reset Hard.

As a result you got empty newBranch, no that late commit and git tree is clean at the HEAD. You are at the branch newBranch (may check with git branch).

Here is two steps action:

  • get the list of commits in 'trash' by

git fsck --lost-found

  • commit the missed commit back to your branch with:

git cherry-pick --ff 43a7a2d

Alexey Nikonov
  • 4,958
  • 5
  • 39
  • 66
0

I found this article on rebase helpful for the same use case.

Lets say you have commits like below

commit 6 [my-feature-branch] | merged onto master commit
commit 5
commit 4 [master]
commit 3
commit 2 [production]
commit 1

And you want only 6 to be included on production.

git checkout my-feature-branch
git rebase production 5

here 5 is the commit hash from where you want to (excluding 5) take the changes and put on production branch.

Mohit Bhagat
  • 245
  • 5
  • 14
0

Coming a little late but this might help future developers.

Assuming you have the following branch structure:

parent --- 1 --- 2 --- 3 --- 4
            \
child        A --- B --- C

target --- X --- Y --- Z

And you want to squash all the child commits onto target, you can merge the child branch into the parent and cherry pick the commit into it:

git checkout parent
git merge --squash child   # Create a temporary commit on parent
git checkout target
git cherry-pick parent     # Apply the squashed commit on target
git checkout parent
git reset --hard HEAD~1    # Delete the commit we created on parent
iMax531
  • 177
  • 1
  • 2
  • 15