- master分支(生产)
- integration分支
- working分支
说到一系列的提交,挑选樱桃是曾经不切实际的。
正如下面提到的Keith Kim所说,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
),那么应该使用B~..D
(而不是B..D
)。
参见"Git create branch from range of previous commits?"以获得示例。注意:从Git 2.9.x/2.10(2016年第三季度)开始,您可以直接在孤立分支(空头)上挑选一系列提交:请参阅“如何将现有分支变为Git中的孤立分支”。这假设
B
不是一个根提交;否则你将会得到一个"未知修订"的错误。
原始答案(2010年1月)
一个rebase --onto
会更好,你可以在你的集成分支上重新播放给定范围的提交,就像Charles Bailey在这里描述的那样。
(另外,在git rebase手册页中查找“这是如何将一个基于一个分支的主题分支移植到另一个分支”的实际示例,以了解git rebase --onto
的用法)
如果你当前的分支是integration:
# 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
时,不要忘记它对后续合并操作有影响,如此处所述。
git rev-list --reverse --topo-order B~..D | while read rev
do
git cherry-pick $rev || break
done
真的:这突显了在使用Git的cherry-picking命令与提交范围时的一个重要细微差别,尤其是在处理合并提交时。警告:不要被上面使用插入符号(
^
)来使范围包含的建议所迷惑!如果父提交是一个合并提交,那么这不会包括
CommitId1
,例如:git cherry-pick CommitId1^..CommitId99
。
在这种情况下,cherry-pick
仍然从CommitId2
开始 - 不知道为什么,但这是我经历过的行为。然而,我发现使用波浪符号(
~
)可以按预期工作,即使CommitId1
的父提交是一个合并提交:git cherry-pick CommitId1~..CommitId99
。
CommitId1
是一个普通的提交,它的唯一父提交(假设为CommitId0
)将成为范围的起点,有效地排除了CommitId1
本身。CommitId1
是一个合并提交,CommitId1^
仍然指向它的第一个父提交。这可能特别令人困惑,因为合并提交本质上合并了两条开发线,而第一个父提交可能在线性意义上不直观地被认为是“前一个”提交。在涉及合并提交的非线性历史中,合并提交的第一个父提交可能不是同一分支中的直接前任。
当使用波浪符(~
)表示法时,如CommitId1~..CommitId99
,它实际上表示“包括CommitId1
并从那里回退一个提交”,在大多数情况下将包括CommitId1
在范围内,正如预期的那样。
考虑以下提交历史,其中M
表示合并提交,每个字母表示不同的提交:
A---B---C-------D---E--CommitId99 <- master
\ /
X---Y---M <- feature (CommitId1 is M)
A
,B
,C
,D
,E
是在 master
分支上的提交。X
,Y
是在一个 feature
分支上的提交。M
是一个合并提交,在 feature
分支上将 master
分支的更改合并到 feature
分支上。在这里,M
是 CommitId1
。当你运行以下命令时:
git cherry-pick CommitId1^..CommitId99
CommitId1
是 M
。CommitId1^
指的是 M
的第一个父提交。M
的第一个父提交是 D
,因为合并提交按照它们合并的顺序列出其父提交。因此,范围 CommitId1^..CommitId99
转换为 D..CommitId99
。M(CommitId1
)被排除在外!
但是,如果你使用 git cherry-pick CommitId1~..CommitId99
,当在范围的上下文中使用时,如 CommitId1~..CommitId99
,它的解释是“从 CommitId1
之前的提交开始,包括到 CommitId99
。”
CommitId1~
指的是在feature
分支中M
之前的提交,即Y
(因为M
是通过从master
到feature
的合并在feature
分支上创建的)。CommitId1~..CommitId99
翻译为Y..CommitId99
。git cherry-pick
时,范围中的~
有效地将范围的起始点移至CommitId1
之前的提交,从而将CommitId1
包括在复制的范围内。这种行为在想要在复制操作中包括合并提交时特别有用。从git v1.7.2开始,cherry pick可以接受一系列的提交:
git cherry-pick
学会了选择一系列的提交(例如cherry-pick A..B
和cherry-pick --stdin
),同样的也适用于git revert
。但是这些命令不像rebase [-i]
命令那样支持更好的序列控制。
正如Gabe Moothart指出的,cherry-pick A..B
不会选取提交 A
(你需要使用 A~1..B
),如果有冲突,git 不会像 rebase 那样自动继续(至少在 1.7.3.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
的范围是不包括最后一个提交的。 - lolololol ol^
,第一个提交将不包含在内。 - Tomas Vancoillie^
表示前一次提交。正如@Tomas所指出的,cherry-pick
的行为会留下提交A。因此,要包括commitA
,你需要选择相应的前一个提交,使用commitA^
。 - SKPSgit cherry-pick "<commitA>^..<commitB>"
。 - Drew Daniels你确定你不想合并这些分支吗?如果工作分支有一些你不想要的最近提交,你可以创建一个新的分支来指向你想要的那个点。
现在,如果你真的想挑选一系列提交,出于某种原因,一个优雅的方法就是拉取补丁集并将其应用到你的新集成分支上:
git format-patch A..B
git checkout integration
git am *.patch
本质上,这正是git-rebase正在做的事情,但无需玩弄技巧。如果需要合并,可以在git-am
中添加--3way
。 如果您完全按照说明操作,请确保在执行此操作的目录中没有其他*.patch文件...
A^
来包括A
。 - Gino Mempin例如:git cherry-pick 3a7322ac^..7d7c123c
假设您在“branchA”上并想要从“branchB”中选择提交记录(给出了范围的起始和结束提交 SHA,左边的提交 SHA 更旧)。整个提交记录范围(包括两端的提交)将被精选到“branchA”中。
官方文档中提供的示例非常有用。
我将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
我正在使用这个工具来重建一个项目的历史记录,该项目包含第三方代码和自定义内容混合在同一个svn主干中。现在,为了更好地理解未来的自定义内容,我将核心第三方代码、第三方模块和自定义内容分别放到它们自己的git分支中。由于我在同一个存储库中有两棵树,但没有共同的祖先,因此git-cherry-pick
在这种情况下非常有用。
git cherry-pick FIRST^..LAST
只适用于简单的情况。
为了在将其合并到集成分支时获得良好的“舒适度”,例如自动跳过已集成的挑选,移植钻石合并,交互控制等等,最好使用rebase。这里有一个答案指出了这一点,但该协议包括一个棘手的git branch -f
和一个临时分支的搬弄。这里提供了一种直接而健壮的方法:
git rebase -i FIRST LAST~0 --onto integration
git rebase @ integration
-i
允许交互式控制。
~0
确保在 LAST 是分支名称时进行分离的变基(不移动/另一个分支)。否则可以省略。第二个变基命令只是以安全的方式将 integration
分支引用向前移动到中间分离的头部,而不会引入新的提交。要重新对包含合并菱形等复杂结构的分支进行变基,请考虑在第一个变基中使用 --rebase-merges
或 --rebase-merges=rebase-cousins
。
我在几天前测试过这个方法,是在阅读了Vonc非常清晰的解释之后。
dev
:A B C D E F G H I Jtarget
:A B C DE
和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
,在交互式编辑器中放置“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
很强大,也很简单,但它
cherry-pick
,它可以“挑选”,但对于更多的提交,它过于冗长,分支将变得太复杂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"
git cherry-pick <commit> -n
-m
选项的父提交,您该如何处理这些提交呢?或者是否有一种方法可以过滤掉这些提交呢? - aug-m
应该为您处理它们,通过选择您为此挑选的樱桃选取所引用的主线。 - VonCgit cherry-pick a87afaaf..asfa789 -m 1
,它将应用于范围内的所有提交。 - augerror: Commit 8fcaf3b61823c14674c841ea88c6067dfda3af48 is a merge but no -m option was given.
我实际上意识到你可以只需使用git cherry-pick --continue
就可以解决问题(但它不会包括父提交)。 - aug