如何樱桃选择一系列提交并合并到另一个分支?

时间:2010-01-03 09:50:24

标签: git git-merge git-cherry-pick

我有以下存储库布局:

  • master branch(production)
  • 集成
  • 工作

我想要实现的是从工作分支中挑选一系列提交并将其合并到集成分支中。我是git的新手,我无法弄清楚如何正确地做到这一点(在一次操作中不提取合并的樱桃选择提交范围)而不会弄乱存储库。关于这个的任何指针或想法?谢谢!

9 个答案:

答案 0 :(得分:715)

当涉及到一系列提交时,挑选 不实用。

mentioned below作为Keith Kim,Git 1.7.2+引入了挑选一系列提交的能力(但您仍然需要了解consequence of cherry-picking for future merge

  

git cherry-pick“学会了选择一系列的提交   (例如“cherry-pick A..B”和“cherry-pick --stdin”),“git revert”也是如此;但是,这些不支持更好的排序控制“rebase [-i]”。

damian comments并警告我们:

  

在“cherry-pick A..B”表单中, A应该早于B
  如果命令错误,命令将无声地失败

如果您想选择 B 范围DB^..D(包括)
> 请参阅“Git create branch from range of previous commits?”作为插图。

由于Jubobs提及in the comments

  

这假设B不是root提交;否则你会收到“unknown revision”错误。

注意:从Git 2.9.x / 2.10(2016年第3季度)开始,您可以直接在孤儿分支(空头)上挑选一系列提交:请参阅“How to make existing branch an orphan in git”。


原始答案(2010年1月)

rebase --onto会更好,您可以在集成分支的顶部重播给定的提交范围,作为Charles Bailey described here
(另请参阅git rebase man page中的“以下是如何将基于一个分支的主题分支移植到另一个分支”,以查看git rebase --onto的实际示例

如果您当前的分支是集成:

# Checkout a new temporary branch at the current location
git checkout -b tmp

# Move the integration branch to the head of the new patchset
git branch -f integration last_SHA-1_of_working_branch_range

# Rebase the patchset onto tmp, the old location of integration
git rebase --onto tmp first_SHA-1_of_working_branch_range~1 integration

这将重播之间的所有内容:

  • first_SHA-1_of_working_branch_range的父级之后(因此为~1):您要重播的第一次提交
  • 最多“integration”(指向您要重播的最后一次提交,来自working分支)

改为“tmp”(指向integration之前指向的地方)

如果在重播其中一个提交时出现任何冲突:

  • 要么解决它并运行“git rebase --continue”。
  • 或跳过此修补程序,而是运行“git rebase --skip
  • 或使用“git rebase --abort”取消所有内容(并在integration分支上放回tmp分支)

rebase --onto之后,integration将返回到集成分支的最后一次提交(即“tmp”分支+所有重放的提交)

通过挑选或rebase --onto,不要忘记它会对后续合并产生影响,如described here


纯粹的“cherry-pick”解决方案是discussed here,其中包含以下内容:

  

如果您想使用补丁方法,那么“git format-patch | git am”和“git cherry”是您的选择。
  目前,git cherry-pick只接受一次提交,但是如果您想在git lingo中选择BD的范围,那么

B^..D

但无论如何,当你需要“重播”一系列提交时,“重播”一词应该会促使你使用Git的“git rev-list --reverse --topo-order B^..D | while read rev do git cherry-pick $rev || break done ”功能。

答案 1 :(得分:127)

从git v1.7.2开始,cherry pick可以接受一系列提交:

  

git cherry-pick学会了选择一系列提交(例如cherry-pick A..Bcherry-pick --stdin),git revert也是如此。但是,这些不支持更好的排序控制rebase [-i]

答案 2 :(得分:25)

您确定不想实际合并分支吗?如果工作分支有一些您不想要的最近提交,您可以在您想要的位置创建一个带有HEAD的新分支。

现在,如果您确实想要挑选一系列提交,无论出于何种原因,这样做的一个优雅方法是只需提取一个补丁集并将其应用到您的新集成分支:

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

这实质上就是git-rebase正在做的事情,但不需要玩游戏。如果需要合并,可以将--3way添加到git-am。如果您逐字按照说明操作,请确保目录中没有其他* .patch文件...

答案 3 :(得分:19)

假设您有2个分支,

" branchA" :包括您要复制的提交(从" commitA"到" commitB"

" branchB" :您希望从" branchA"

传输提交的分支

1)

 git checkout <branchA>

2)获取&#34; commitA&#34;的ID和&#34; commitB&#34;

3)

git checkout <branchB>

4)

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

5)如果您遇到冲突,请解决并输入

git cherry-pick --continue

继续采摘樱桃。

答案 4 :(得分:8)

我将VonC's code包装到一个简短的bash脚本git-multi-cherry-pick中,以便于运行:

#!/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 

我正在使用这个,因为我重建了一个项目的历史记录,该项目的第三方代码和自定义在同一个svn主干中混合在一起。我现在将核心第三方代码,第三方模块和自定义分离到他们自己的git分支上,以便更好地理解未来的自定义。 git-cherry-pick在这种情况下很有用,因为我在同一个存储库中有两棵树,但是没有共享的祖先。

答案 5 :(得分:3)

以上所有选项都会提示您解决合并冲突。如果要合并为团队提交的更改,则很难解决开发人员的合并冲突并继续进行。但是,“git merge”将一次性完成合并,但您无法通过一系列修订作为参数。我们必须使用“git diff”和“git apply”命令来进行转速的合并范围。我观察到如果补丁文件包含太多文件的差异,“git apply”将失败,因此我们必须为每个文件创建一个补丁然后应用。请注意,该脚本将无法删除源分支中删除的文件。这是一种罕见的情况,您可以从目标分支手动删除此类文件。如果它不能应用补丁,“git apply”的退出状态不为零,但是如果你使用-3way选项,它将回退到3路合并,你不必担心这个失败。

以下是剧本。

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"

答案 6 :(得分:2)

几天前,在阅读了关于Vonc的非常清楚的解释后,我进行了测试。

我的脚步

开始

  • 分支dev:A B C D E F G H I J
  • 分支target:A B C D
  • 我既不想E也不要H

复制分支dev_feature_wo_E_H中没有步骤E和H的步骤

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff D,其中我将dropE的{​​{1}}放在了基准编辑器中
  • 解决冲突,继续并H

在目标上复制分支commit的步骤。

  • dev_feature_wo_E_H
  • git checkout target
  • 解决冲突,继续并git merge --no-ff --no-commit dev_feature_wo_E_H

一些评论

  • 我之所以这样做,是因为前几天commit
  • cherry-pick功能强大且简单,但

    • 它创建重复的提交
    • 并且当我想要git cherry-pick时,我必须解决初始提交和重复提交的冲突,因此对于一两个merge来说,“樱桃采摘”是可以的,但对于更多的它也是详细,分支将变得太复杂
  • 在我看来,我完成的步骤比cherry-pick
  • 更清晰

答案 7 :(得分:1)

$ git cherry-pick start_commit_sha_id ^ .. end_commit_sha_id

例如git cherry-pick 3a7322ac^..7d7c123c

假设,您位于branchA上要选择提交的位置(给出了该范围的开始和结束提交SHA,给出的左侧提交SHA较旧)来自branchB。整个提交范围(包括两端)都将在branchA中挑选。

官方文档中提供的examples非常有用。

答案 8 :(得分:0)

git cherry-pick FIRST^..LAST仅适用于简单情况。

要实现一个体面的“将其合并到集成分支中”,同时在诸如自动跳过已集成的镐头,移植钻石合并,交互式控制等方面保持舒适性,最好使用rebase。这里的一个答案指出,但是该协议包括了一个简单的git branch -f和一个临时分支。这是一个简单而健壮的方法:

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

-i允许交互式控制。 如果LAST是分支名称,则~0确保分离的基准(不移动/另一个分支)。否则可以省略。第二个rebase命令只是以安全的方式将integration分支引用移到中间的分离头中-它不引入新的提交。要使用合并菱形等对复杂结构进行基础调整,请在第一个基础调整中考虑使用--rebase-merges--rebase-merges=rebase-cousins