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


640

我有以下存储库布局:

  • 主分支(生产)
  • 积分
  • 工作中

我想要实现的是从工作分支中挑选一系列提交并将其合并到集成分支中。我对git很陌生,我不知道如何准确地做到这一点(在一个操作中选择合并范围而不是合并中的提交范围)而不弄乱存储库。关于此有任何指示或想法吗?谢谢!


Answers:


808

当涉及到一系列提交时,挑选樱桃 不实际的。

正如下面提到基思·金,Git的1.7.2+推出的能力樱桃挑选范围的提交(但你仍然需要注意的的后果樱桃采摘为将来合并

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

达米安 评论并警告我们:

在“ cherry-pick A..B”形式中,A应早于B
如果它们的顺序错误,命令将无提示地失败

如果你想挑选范围B通过D(含),这将是B^..D
请参阅“ Git从先前提交的范围创建分支? ”作为插图。

正如Jubobs 在评论中提到

假设这B不是根提交;unknown revision否则会出现“ ”错误。

注意:从Git 2.9.x / 2.10(2016年第三季度)开始,您可以直接在孤儿分支(空头)上挑选一系列提交:请参阅“ 如何在git中使现有分支成为孤儿 ”。


原始答案(2010年1月)

一个rebase --onto会更好,在那里你重播提交你的集成分支之上的一定范围,如查尔斯·贝利在这里描述
(另请在git rebase手册页中查找“这是将基于一个分支的主题分支移植到另一个分支的方法,以查看的实际示例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 --ontointegration将返回到集成分支的最后一次提交(即“ tmp”分支+所有重播的提交)

随着樱桃采摘或者rebase --onto,不要忘了对后续的合并影响,因为这里描述


这里讨论了一个纯的“ cherry-pick”解决方案,其中涉及到以下内容:

如果您想使用补丁方法,则可以选择“ git format-patch | git am”和“ git cherry”。
目前,git cherry-pick仅接受一次提交,但是如果您要B通过D它来选择范围,则使用B^..Dgit lingo,因此

git rev-list --reverse --topo-order B^..D | while read rev 
do 
  git cherry-pick $rev || break 
done 

但是无论如何,当您需要“重播”一系列提交时,“重播”一词应促使您使用rebaseGit 的“ ”功能。


1
如果您的提交中有需要该-m选项的父母,那么如何处理这些提交?还是有办法过滤掉这些提交?
八月

@aug -m应该为您处理这些问题,方法是选择-m为此摘樱桃选择的参数所引用的主线。
VonC

问题是,如果您正在挑选一系列提交,它将正确选择父提交,但是当它到达正常提交时,它将失败并表示提交不是合并。我想我的问题更好地表述为:如何在-m仅选择提交范围时击中父提交时使其通过选项?现在如果我通过-mgit cherry-pick a87afaaf..asfa789 -m 1它适用于范围内的所有提交。
2016年8

@aug奇怪,我没有重现此问题。您的git版本是什么,而whayt是您看到的确切错误消息吗?
VonC

啊,我正在运行git版本2.6.4(Apple Git-63)。我看到的错误类似于error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.我实际上意识到您可以做到的,git cherry-pick --continue并且可以(但不包括父提交)
2016年

136

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

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


103
请注意,cherry-pick A..B它将不会获得提交A(您将需A~1..B要这样做),并且如果有任何冲突,git不会像rebase一样自动继续(至少从1.7.3.1开始)
Gabe Moothart 2011年

3
还需要注意的是,git cherry-pick A..B C它并没有像您期望的那样天真地工作。它不会选择范围内的所有内容A..B并提交C!为此,您需要先分成两行,git cherry-pick A..B然后再分为git cherry-pick C。因此,只要有范围,就需要分别执行。
MicroVirus 2013年

42

假设您有2个分支,

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

“ branchB”:要从“ branchA”转移提交的分支

1)

 git checkout <branchA>

2)获取“ commitA”和“ commitB”的ID

3)

git checkout <branchB>

4)

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

5)如果有冲突,请解决并输入

git cherry-pick --continue

继续进行摘樱桃的过程。


像魅力一样工作!非常感谢!
snukone

28

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

现在,无论出于何种原因,如果您确实确实想选择一系列提交,一种简便的方法是仅提取补丁集并将其应用于新的集成分支:

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

无论如何,这本质上就是git-rebase所做的,但是不需要玩游戏。如果需要合并,可以添加--3waygit-am。如果遵循逐字说明,请确保执行此操作的目录中没有其他* .patch文件...


1
请注意,与其他修订版范围一样,必须A^包含A
Gino Mempin

9

我将VonC的代码包装到一个简短的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 

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


3

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

下面是脚本。

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"

2

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

我的脚步

开始

  • 分公司dev:ABCDEFGHIJ
  • 分行target:ABCD
  • 我不想E,也不H

在分支中没有步骤E和H的情况下复制特征的步骤 dev_feature_wo_E_H

  • git checkout dev
  • git checkout -b dev_feature_wo_E_H
  • git rebase --interactive --rebase-merges --no-ff D我在rebase编辑器drop前面E和里面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,“樱桃采摘”是可以的,但更多的则是太冗长,分支会变得太复杂
  • 在我看来,我完成的步骤比 git rebase --onto

1
好帖子。已投票。它使我想起stackoverflow.com/a/38418941/6309。我回顾了2012年采摘樱桃的弊端:stackoverflow.com/a/13524494/6309
VonC

1

另一种选择可能是与我们的策略合并到该范围之前的提交,然后与该范围的最后一次提交(或分支到最后一个提交)进行“正常”合并。因此,假设只有2345和3456的master提交要合并到功能分支中:

主:
1234
2345
3456
4567

在功能分支中:

git merge -s我们的4567
git merge 2345
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.