如何选择一系列提交并将它们合并到另一个分支中?

我有以下存储库布局:

  • 总科(生产)
  • 集成
  • 工作

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

469294 次浏览

当涉及到一系列提交时,樱桃采摘并不实用。

作为下文提到 byKeith Kim,Git 1.7.2+引入了选择一系列提交的能力(但您仍然需要注意为将来的合并采摘樱桃的后果

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

达米安评论数并警告我们:

在“cherry-pick A..B”形式中,#1应该比#2大
如果它们的顺序错误,命令将静默失败

如果你想选择范围#0至#1(包括#0),那就是B^..D(而不是B..D)。
请参阅“git从以前的提交范围创建分支?”作为示例。

正如Jubobs提到在评论

这假设B不是根提交;否则你会得到一个“unknown revision”错误。

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


原答复(2010年1月)

Arebase --onto会更好,您可以在集成分支之上重播给定的提交范围,作为Charles Bailey在这里描述
(另外,在git rebase手册页中查找“这是您如何将基于一个分支的主题分支移植到另一个分支”,以查看git rebase --onto的实际示例)

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

# Checkout a new temporary branch at the current locationgit checkout -b tmp
# Move the integration branch to the head of the new patchsetgit branch -f integration last_SHA-1_of_working_branch_range
# Rebase the patchset onto tmp, the old location of integrationgit 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,不要忘记它会对后续合并产生影响,如这里描述


一个纯“cherry-pick”的解决方案是这里讨论,它涉及如下内容:

如果您想使用补丁方法,那么“git format-patch|git am”和“git Cherry”是您的选择。
目前,git cherry-pick只接受一次提交,但如果你想选择范围BD,那在git行话中就是B^..D,所以

git rev-list --reverse --topo-order B^..D | while read revdogit cherry-pick $rev || breakdone

但无论如何,当你需要“重播”一系列提交时,“重播”这个词应该促使你使用Git的“rebase”功能。

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

现在,如果你真的想选择一系列提交,不管出于什么原因,一个优雅的方法就是拉取一个补丁集并将其应用到你的新集成分支:

git format-patch A..Bgit checkout integrationgit am *.patch

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

我将VonC的代码包装成一个简短的bash脚本git-multi-cherry-pick,以便轻松运行:

#!/bin/bash
if [ -z $1 ]; thenecho "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 revdogit cherry-pick $rev || breakdone

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

从git v1.7.2开始,樱桃选择可以接受一系列提交:

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

正如Gabe Moothart所指出的那样,cherry-pick A..B不会得到提交A(你需要A~1..B),如果有任何冲突,git不会像rebase那样自动继续(至少1.7.3.1)。

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

master:1234234534564567

在功能分支:

git merge -s ours 4567git merge 2345

以上所有选项都将提示您解决合并冲突。如果您正在合并为团队提交的更改,则很难从开发人员那里解决合并冲突并继续进行。然而,“git合并”将一次完成合并,但您不能将一系列修订作为参数传递。我们必须使用“git diff”和“git应用”命令来执行rev的合并范围。我观察到,如果补丁文件有太多文件的diff,“git应用”将失败,因此我们必须为每个文件创建一个补丁,然后应用。请注意,脚本将无法删除源分支中删除的文件。这是一种罕见的情况,您可以手动从目标分支中删除此类文件。如果无法应用补丁,“git应用”的退出状态不为零,但是如果您使用-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=$1END=$2
echo Start version: $STARTecho End version: $END
mkdir -p ~/tempecho > /tmp/status#get filesgit --no-pager  diff  --name-only ${START}..${END} > ~/temp/filesecho > ~/temp/error.log# merge every filefor file in `cat  ~/temp/files`dogit --no-pager diff --binary ${START}..${END} $file > ~/temp/git-diffif [ $? -ne 0 ]then#      Diff usually fail if the file got deletedecho Skipping the merge: git diff command failed for $file >> ~/temp/error.logecho Skipping the merge: git diff command failed for $fileecho "STATUS: FAILED $file" >>  /tmp/statusecho "STATUS: FAILED $file"# skip the merge for this file and continue the merge for othersrm -f ~/temp/git-diffcontinuefi
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 failureecho "git apply command filed for $file"fiechoSTATUS=`git status -s $file`

if [ ! "$STATUS" ]then#   status is null if the merged diffs are already present in the target fileecho "STATUS:NOT_MERGED $file"echo "STATUS: NOT_MERGED $file$"  >>  /tmp/statuselse#     3 way merge is successfulecho STATUS: $STATUSecho "STATUS: $STATUS"  >>  /tmp/statusfidone
echo GIT merge failed for below listed files
cat ~/temp/error.log
echo "Git merge status per file is available in /tmp/status"

假设你有两个分支,

“分支A”:包括您要复制的提交(从“委员会A”到“委员会B”)

"分支B":您希望从"分支A"转移提交的分支

1)

 git checkout <branchA>

2)获取“委员会”和“委员会”的ID

3)

git checkout <branchB>

4)

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

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

git cherry-pick --continue

继续樱桃采摘过程。

几天前,在阅读了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,我在rebase编辑器中将drop放在EH的前面
  • 解决冲突,继续和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更清晰

git樱桃选择start_commit_sha_id^…end_commit_sha_id

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

假设你在branchA上,你想从branchB中选择提交(给定范围的开始和结束提交SHA,并且左提交SHA较旧)。整个提交范围(包括在内)将在branchA中选择。

官方留档中给出的示例非常有用。

git cherry-pick FIRST^..LAST仅适用于简单的场景。

为了实现一个体面的“将其合并到集成分支”,同时对已经集成的选择的自动跳过、移植钻石合并、交互式控制……更好地使用rebase。这里的一个答案指出了这一点,然而该协议包括一个冒险的git branch -f和一个临时分支的杂耍。这里有一个直接的健壮方法:

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

-i允许交互式控制。~0确保了一个分离的rebase(不移动/另一个分支),以防LAST是一个分支名称。否则可以省略。第二个rebase命令只是以安全的方式将integration分支ref向前移动到中间分离的头部-它不会引入新的提交。要使用合并钻石等重新建立复杂结构的基础,请考虑第一个rebase中的--rebase-merges--rebase-merges=rebase-cousins

如果你只有几个提交并且想要选择,你可以这样做

git cherry-pick <commit> -n

在这些提交上,然后将它们变成一个新的提交。

-n不会自动创建提交,而只是阶段更改,因此您可以继续挑选或更改该提交中的文件。