【问题标题】:How to cherry-pick a range of commits and merge them into another branch?如何挑选一系列提交并将它们合并到另一个分支中?
【发布时间】:2010-12-31 22:56:09
【问题描述】:

我有以下存储库布局:

  • master 分支(生产)
  • 集成
  • 工作

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

【问题讨论】:

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


【解决方案1】:

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

作为Keith Kimmentioned below,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]”。

damiancomments 并警告我们:

在“cherry-pick A..B”表单中,A 应早于 B
如果顺序错误,命令将静默失败强>。

如果您想选择 范围 BD(包括 B,那将是 B^..D(而不是 B..D) .
请参阅“Git create branch from range of previous commits?”作为插图。

正如Jubobs 提到in the comments

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

注意:从 Git 2.9.x/2.10(2016 年第三季度)开始,您可以直接在孤立分支(空头)上挑选一系列提交:请参阅“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 --ontointegration 将回到集成分支的最后一次提交(即“tmp”分支+所有重放的提交)

使用cherry-picking 或rebase --onto,不要忘记它会对后续合并产生影响,如described here


一个纯粹的“cherry-pick”解决方案是discussed here,它会涉及到:

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

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

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

【讨论】:

  • 如果您的提交的父项需要-m 选项,您如何处理这些提交?或者有没有办法过滤掉这些提交?
  • @aug -m 应该为您处理它们,方法是选择您为此挑选的 -m 参数引用的主线。
  • 问题是,如果您正在挑选一系列提交,它会正确挑选父提交,但是当它达到正常提交时,它会失败并说提交不是合并。我想我的问题更好地表述为如何使它通过-m 选项,只有当它在挑选提交范围时遇到父提交时?现在,如果我通过 -mgit cherry-pick a87afaaf..asfa789 -m 1 它适用于范围内的所有提交。
  • @aug 奇怪,我没有重现这个问题。您的 git 版本是什么,您看到的确切错误消息是什么?
  • 啊,我正在运行 git 版本 2.6.4 (Apple Git-63)。我看到的错误类似于error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given. 我实际上意识到你可以只使用git cherry-pick --continue 就可以了(但它不包括父提交)
【解决方案2】:

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

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

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

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

【讨论】:

  • 请注意,与其他修订范围相同,它需要为A^ 才能包含A
【解决方案3】:

我将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 主干中混合了第 3 方代码和自定义项。我现在将核心第 3 方代码、第 3 方模块和自定义拆分到他们自己的 git 分支上,以便更好地理解未来的自定义。 git-cherry-pick 在这种情况下很有帮助,因为我在同一个存储库中有两棵树,但没有共享祖先。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 请注意 cherry-pick A..B 不会得到提交 A(你需要 A~1..B ),如果有任何冲突,git 不会像 rebase 那样自动继续(至少从 1.7 开始)。 3.1)
    • 还需要注意的是,git cherry-pick A..B C 并没有像您期望的那样天真地工作。它不会选择A..B 范围内的所有内容并提交C!为此,您需要分成两行,首先是git cherry-pick A..B,然后是git cherry-pick C。所以,只要有范围,就需要单独执行。
    【解决方案5】:

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

    掌握: 1234 2345 3456 4567

    在功能分支中:

    git 合并 -s 我们的 4567 git 合并 2345

    【讨论】:

      【解决方案6】:

      以上所有选项都会提示您解决合并冲突。如果您正在合并为团队提交的更改,则很难从开发人员那里解决合并冲突并继续。但是,“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"
      

      【讨论】:

        【解决方案7】:

        假设你有 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
        

        继续挑选过程。

        【讨论】:

        • 工作就像魅力!非常感谢!
        • "git cherry-pick ^.."命令中的"^"在4)中有什么作用?
        • 有人请编辑cherry-pick 范围不包括在内的帖子。
        • @JVM 当你在没有^ 的范围内使用cherry-pick 时,将不包含第一个提交
        【解决方案8】:

        在阅读了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 更清晰

        【讨论】:

        【解决方案9】:

        git cherry-pick start_commit_sha_id^..end_commit_sha_id

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

        假设您在 branchA 上,您想从 @ 选择提交(开始和结束提交 SHA 的范围已给出,而左提交 SHA 较旧) 987654324@。整个提交范围(包括两者)都将在 branchA 中挑选出来。

        官方文档中给出的examples还是蛮有用的。

        【讨论】:

          【解决方案10】:

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

          要实现体面的“将其合并到集成分支中”,同时使用自动跳过已经集成的选择、移植菱形合并、交互式控制等操作感到舒适......)最好使用变基。这里的一个答案指出了这一点,但是该协议包括一个冒险的git branch -f 和一个临时分支的杂耍。这是一个直接稳健的方法:

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

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

          【讨论】:

            猜你喜欢
            • 2020-12-11
            • 2011-11-24
            • 1970-01-01
            • 2018-03-12
            • 2022-12-16
            • 2016-03-23
            • 1970-01-01
            • 1970-01-01
            • 2011-09-16
            相关资源
            最近更新 更多