[git] How to cherry pick a range of commits and merge into another branch?

I have the following repository layout:

  • master branch (production)
  • integration
  • working

What I want to achieve is to cherry pick a range of commits from the working branch and merge it into the integration branch. I pretty new to git and I can't figure out how to exactly do this (the cherry picking of commit ranges in one operation not the merging) without messing the repository up. Any pointers or thoughts on this? Thanks!

This question is related to git git-merge git-cherry-pick

The answer is


Another option might be to merge with strategy ours to the commit before the range and then a 'normal' merge with the last commit of that range (or branch when it is the last one). So suppose only 2345 and 3456 commits of master to be merged into feature branch:

master:
1234
2345
3456
4567

in feature branch:

git merge -s ours 4567
git merge 2345

I wrapped VonC's code into a short bash script, git-multi-cherry-pick, for easy running:

#!/bin/bash

if [ -z $1 ]; then
    echo "Equivalent to running git-cherry-pick on each of the commits in the range specified.";
    echo "";
    echo "Usage:  $0 start^..end";
    echo "";
    exit 1;
fi

git rev-list --reverse --topo-order $1 | while read rev 
do 
  git cherry-pick $rev || break 
done 

I'm currently using this as I rebuild the history of a project that had both 3rd-party code and customizations mixed together in the same svn trunk. I'm now splitting apart core 3rd party code, 3rd party modules, and customizations onto their own git branches for better understanding of customizations going forward. git-cherry-pick is helpful in this situation since I have two trees in the same repository, but without a shared ancestor.


All the above options will prompt you to resolve merge conflicts. If you are merging changes committed for a team, it is difficult to get resolved the merge conflicts from developers and proceed. However, "git merge" will do the merge in one shot but you can not pass a range of revisions as argument. we have to use "git diff" and "git apply" commands to do the merge range of revs. I have observed that "git apply" will fail if the patch file has diff for too many file, so we have to create a patch per file and then apply. Note that the script will not be able to delete the files that are deleted in source branch. This is a rare case, you can manually delete such files from target branch. The exit status of "git apply" is not zero if it is not able to apply the patch, however if you use -3way option it will fall back to 3 way merge and you don't have to worry about this failure.

Below is the script.

enter code here



  #!/bin/bash

    # This script will merge the diff between two git revisions to checked out branch
    # Make sure to cd to git source area and checkout the target branch
    # Make sure that checked out branch is clean run "git reset --hard HEAD"


    START=$1
    END=$2

    echo Start version: $START
    echo End version: $END

    mkdir -p ~/temp
    echo > /tmp/status
    #get files
    git --no-pager  diff  --name-only ${START}..${END} > ~/temp/files
    echo > ~/temp/error.log
    # merge every file
    for file in `cat  ~/temp/files`
    do
      git --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diff
      if [ $? -ne 0 ]
      then
#      Diff usually fail if the file got deleted 
        echo Skipping the merge: git diff command failed for $file >> ~/temp/error.log
        echo Skipping the merge: git diff command failed for $file
        echo "STATUS: FAILED $file" >>  /tmp/status
        echo "STATUS: FAILED $file"
    # skip the merge for this file and continue the merge for others
        rm -f ~/temp/git-diff
        continue
      fi

      git apply  --ignore-space-change --ignore-whitespace  --3way --allow-binary-replacement ~/temp/git-diff

      if [ $? -ne 0 ]
       then
#  apply failed, but it will fall back to 3-way merge, you can ignore this failure
         echo "git apply command filed for $file"
       fi
       echo
       STATUS=`git status -s $file`


       if [ ! "$STATUS" ]
       then
#   status is null if the merged diffs are already present in the target file
         echo "STATUS:NOT_MERGED $file"
         echo "STATUS: NOT_MERGED $file$"  >>  /tmp/status
       else
#     3 way merge is successful
         echo STATUS: $STATUS
         echo "STATUS: $STATUS"  >>  /tmp/status
       fi
    done

    echo GIT merge failed for below listed files

    cat ~/temp/error.log

    echo "Git merge status per file is available in /tmp/status"

As of git v1.7.2 cherry pick can accept a range of commits:

git cherry-pick learned to pick a range of commits (e.g. cherry-pick A..B and cherry-pick --stdin), so did git revert; these do not support the nicer sequencing control rebase [-i] has, though.


Assume that you have 2 branches,

"branchA" : includes commits you want to copy (from "commitA" to "commitB"

"branchB" : the branch you want the commits to be transferred from "branchA"

1)

 git checkout <branchA>

2) get the IDs of "commitA" and "commitB"

3)

git checkout <branchB>

4)

git cherry-pick <commitA>^..<commitB>

5) In case you have a conflict, solve it and type

git cherry-pick --continue

to continue the cherry-pick process.


Are you sure you don't want to actually merge the branches? If the working branch has some recent commits you don't want, you can just create a new branch with a HEAD at the point you want.

Now, if you really do want to cherry-pick a range of commits, for whatever reason, an elegant way to do this is to just pull of a patchset and apply it to your new integration branch:

git format-patch A..B
git checkout integration
git am *.patch

This is essentially what git-rebase is doing anyway, but without the need to play games. You can add --3way to git-am if you need to merge. Make sure there are no other *.patch files already in the directory where you do this, if you follow the instructions verbatim...


I have tested that some days ago, after reading the very clear explanation of Vonc.

My steps

Start

  • Branch dev : A B C D E F G H I J
  • Branch target: A B C D
  • I don't want E nor H

Steps to copy features without the step E and H in the branch dev_feature_wo_E_H

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff D where I put drop front of E and H in the rebase editor
  • resolve conflicts, continue and commit

Steps to copy the the branch dev_feature_wo_E_H on target.

  • git checkout target
  • git merge --no-ff --no-commit dev_feature_wo_E_H
  • resolve conflicts, continue and commit

Some remarks

  • I've done that because of too much cherry-pick in the days before
  • git cherry-pick is powerful and simple but

    • it creates duplicates commits
    • and when I want to merge I have to resolve conflicts of the initial commits and duplicates commits, so for one or two cherry-pick, it's OK to "cherry-picking" but for more it's too verbose and the branch will become too complex
  • In my mind the steps I've done are more clear than git rebase --onto

git cherry-pick FIRST^..LAST works only for simple scenarios.

To achieve a decent "merge it into the integration branch" while having the usal comfort with things like auto-skipping of already integrated picks, transplanting diamond-merges, interactive control ...) better use a rebase. One answer here pointed to that, however the protocol included a dicey git branch -f and a juggling with a temp branch. Here a straight robust method:

git rebase -i FIRST LAST~0 --onto integration
git rebase @ integration

The -i allows for interactive control. The ~0 ensures a detached rebase (not moving the / another branch) in case LAST is a branch name. It can be omitted otherwise. The second rebase command just moves the integration branch ref in safe manner forward to the intermediate detached head - it doesn't introduce new commits. To rebase a complex structure with merge diamonds etc. consider --rebase-merges or --rebase-merges=rebase-cousins in the first rebase.


$ git cherry-pick start_commit_sha_id^..end_commit_sha_id

e.g. git cherry-pick 3a7322ac^..7d7c123c

Assuming you are on branchA where you want to pick commits (start & end commit SHA for the range is given and left commit SHA is older) from branchB. The entire range of commits (both inclusive) will be cherry picked in branchA.

The examples given in the official documentation are quite useful.


Examples related to git

Does the target directory for a git clone have to match the repo name? Git fatal: protocol 'https' is not supported Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) git clone: Authentication failed for <URL> destination path already exists and is not an empty directory SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443 GitLab remote: HTTP Basic: Access denied and fatal Authentication How can I switch to another branch in git? VS 2017 Git Local Commit DB.lock error on every commit How to remove an unpushed outgoing commit in Visual Studio?

Examples related to git-merge

Abort a Git Merge Git pull - Please move or remove them before you can merge Git: How configure KDiff3 as merge tool and diff tool Git: How to pull a single file from a server repository in Git? How to resolve git error: "Updates were rejected because the tip of your current branch is behind" error: Your local changes to the following files would be overwritten by checkout Please enter a commit message to explain why this merge is necessary, especially if it merges an updated upstream into a topic branch How to merge specific files from Git branches git remove merge commit from history The following untracked working tree files would be overwritten by merge, but I don't care

Examples related to git-cherry-pick

Git Cherry-Pick and Conflicts Abort a git cherry-pick? What does cherry-picking a commit with Git mean? Merge up to a specific commit How to cherry pick from 1 branch to another How to cherry pick a range of commits and merge into another branch? Partly cherry-picking a commit with Git How can I selectively merge or pick changes from another branch in Git?