我有以下存储库布局:
主分支(生产)
一体化
在职的
我想要实现的是从工作分支中挑选一系列提交并将其合并到集成分支中。我对 git 很陌生,我无法弄清楚如何在不弄乱存储库的情况下准确地做到这一点(在一个操作中挑选提交范围,而不是合并)。对此有任何指示或想法吗?谢谢!
git cherry-pick <one-sha-before-the-oldest-sha-to-pick>..<sha-of-latest-to-pick>
当涉及到一系列提交时,挑选樱桃是不切实际的。
作为 Keith Kim 的 mentioned 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]" 有。
在“cherry-pick A..B”形式中,A 应该比 B 更老。如果它们的顺序错误,命令将静默失败。
如果您想选择 范围 B
到 D
(包括 B
),则应为 B^..D
(而不是 B..D
)。
请参阅“Git create branch from range of previous commits?”作为说明。
正如 Jubobs 提到的 in the comments:
这假设 B 不是根提交;否则,您将收到“未知修订”错误。
注意:从 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):您要重播的第一个提交
直到“集成”(它指向您要从工作分支重播的最后一次提交)
到“tmp
”(指向 integration
之前指向的位置)
如果在重放其中一个提交时存在任何冲突:
要么解决它并运行“git rebase --continue”。
或者跳过这个补丁,而是运行“git rebase --skip”
或使用“git rebase --abort”取消所有内容(并将集成分支放回 tmp 分支)
在 rebase --onto
之后,integration
将返回到集成分支的最后一次提交(即“tmp
”分支 + 所有重放的提交)
使用cherry-picking 或rebase --onto
,不要忘记它会对后续合并产生影响,如described here。
一个纯粹的“cherry-pick
”解决方案是 discussed here,并且会涉及以下内容:
如果你想使用补丁方法,那么“git format-patch|git am”和“git cherry”是你的选择。目前, git cherry-pick 只接受一个提交,但如果你想选择从 B 到 D 的范围,那就是 git lingo 中的 B^..D,所以
git rev-list --reverse --topo-order B^..D | while read rev
do
git cherry-pick $rev || break
done
但无论如何,当您需要“重播”一系列提交时,“重播”一词应该会促使您使用 Git 的“rebase
”功能。
从 git v1.7.2 开始,cherry pick 可以接受一系列提交:
git cherry-pick 学会了选择一系列提交(例如cherry-pick A..B 和cherry-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
。所以,只要你有一个范围,你就需要单独执行它。
假设你有 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
继续挑选樱桃的过程。
cherry-pick
范围不包括在内的帖子。
^
的cherry-pick 时,将不包括第一次提交
你确定你不想真正合并分支吗?如果工作分支有一些你不想要的最近提交,你可以在你想要的位置创建一个带有 HEAD 的新分支。
现在,如果您真的想挑选一系列提交,无论出于何种原因,一个优雅的方法就是拉出一个补丁集并将其应用到您的新集成分支:
git format-patch A..B
git checkout integration
git am *.patch
这基本上就是 git-rebase 正在做的事情,但不需要玩游戏。如果需要合并,可以将 --3way
添加到 git-am
。如果您逐字按照说明进行操作,请确保您执行此操作的目录中没有其他 *.patch 文件...
A^
才能包含 A
。
git cherry-pick start_commit_sha_id^..end_commit_sha_id
例如git cherry-pick 3a7322ac^..7d7c123c
假设您在 branchA
上要从 branchB
中选择提交(开始和结束提交 SHA 的范围已给出,而左提交 SHA 较旧) .整个提交范围(包括两者)将在 branchA
中挑选出来。
官方文档中给出的examples非常有用。
我将 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
在这种情况下很有帮助,因为我在同一个存储库中有两棵树,但没有共享祖先。
在阅读了Vonc的非常清晰的解释后,我几天前已经对此进行了测试。
我的脚步
开始
分支开发:ABCDEFGHIJ
分支目标:ABCD
我不想要E也不H
在分支 dev_feature_wo_E_H 中没有步骤 E 和 H 的复制特征的步骤
git结帐开发
git checkout -b dev_feature_wo_E_H
git rebase --interactive --rebase-merges --no-ff D 我在rebase编辑器中放置E和H的前面
解决冲突,继续并提交
在目标上复制分支 dev_feature_wo_E_H 的步骤。
git结帐目标
git merge --no-ff --no-commit dev_feature_wo_E_H
解决冲突,继续并提交
一些备注
我这样做是因为前几天樱桃采摘太多
git cherry-pick 功能强大且简单,但它会创建重复提交,当我想合并时,我必须解决初始提交和重复提交的冲突,因此对于一两个樱桃选择,“樱桃采摘”是可以的,但是更多它太冗长了,分支会变得太复杂
它创建重复提交
当我想合并时,我必须解决初始提交和重复提交的冲突,所以对于一两个樱桃采摘,“樱桃采摘”是可以的,但对于更多它来说太冗长并且分支会变得太复杂
在我看来,我所做的步骤比 git rebase --onto 更清楚
git cherry-pick FIRST^..LAST
仅适用于简单场景。
为了实现一个体面的“将其合并到集成分支中”,同时让用户感到舒适,例如自动跳过已经集成的选择、移植菱形合并、交互式控制......)更好地使用变基。这里的一个答案指出了这一点,但是该协议包括一个冒险的 git branch -f
和一个临时分支的杂耍。这里有一个直接稳健的方法:
git rebase -i FIRST LAST~0 --onto integration
git rebase @ integration
-i
允许交互式控制。如果 LAST 是分支名称,~0
确保分离的变基(不移动 / 另一个分支)。否则可以省略。第二个变基命令只是将 integration
分支引用以安全的方式向前移动到中间分离的头 - 它不会引入新的提交。要使用合并菱形等对复杂结构进行 rebase,请在第一个 rebase 中考虑 --rebase-merges
或 --rebase-merges=rebase-cousins
。
以上所有选项都会提示您解决合并冲突。如果您正在合并为团队提交的更改,则很难从开发人员那里解决合并冲突并继续。但是,“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"
另一种选择可能是与我们的策略合并到范围之前的提交,然后与该范围的最后一个提交(或最后一个时的分支)进行“正常”合并。所以假设只有 2345 和 3456 个 master 提交合并到特性分支中:
master: 1234 2345 3456 4567
在功能分支中:
git merge -s ours 4567 git merge 2345
-m
选项,您如何处理这些提交?或者有没有办法过滤掉这些提交?-m
应该为您处理它们,方法是选择您为此挑选的-m
参数引用的主线。-m
选项,只有当它在挑选提交范围时遇到父提交时?现在,如果我像git cherry-pick a87afaaf..asfa789 -m 1
一样通过-m
,它适用于该范围内的所有提交。error: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.
我实际上意识到您可以只使用git cherry-pick --continue
就可以了(但它不包括父提交)