我想从一个分支到另一个分支挑选单个提交。我希望文件重命名非常常见,但仍然希望能够在没有人为干预的情况下应用更改。
由于内置的cherry-pick命令不会接缝检测重命名(至少在我的测试用例中是这样),尤其是在与对重命名文件的修改结合使用时。
我尝试了一点,最终想出了一个涉及两次重新基线操作的解决方案。
假设我有一个名为target的分支,指向我要应用cherry-pick的提交。我要挑选的提交由一个名为source的分支指向。
然后我执行以下命令:
- 创建与源指向同一提交的分支sourceTemp(因为我想保留分支源)
git rebase --strategy="recursive" --strategy-option="rename-threshold=30" target sourceTemp
(可能使用另一个阈值;测试文件非常小,因此更改相对较大)git rebase --onto target sourceTemp~ sourceTemp
这仅适用于分支源中的最后一次提交对目标所做的更改。
我还在github:上进行了测试
https://github.com/fraschfn/cherry-pick
我想知道的是,这种方法是否可行,或者它是否只在我的简单测试设置中起作用!
更新:替代方法
我将补丁重新设置为源和目标的合并基础:
启动情况
A - B <--- target
/
M
C - D <--- source
我想把D选到B上。
创建新分支补丁后,将D重定为M
A - B <--- target / M - D' <--- patch C - D <--- source
合并C和D'以获得源的替代品
合并B和D'以获得目标的修补版本
A - B <--- target / / E <--- patched target / / M - D' <--- patch F <--- new source (same snapshot as source different history) / C - D <--- source (will be discarded)
优点是E和F现在可以毫无问题地合并。另一种方式:尽早在层次结构中包含补丁,这样就不会创建D,而是直接创建D',并为自己节省重新基准。
上一版本的优势在于,您可以合并"新源"one_answers"修补的目标"这两个分支,并且它会起作用(如果源和目标的合并当然会起作用),而不会两次引入同一变更集,因为git知道这是由于合并操作将变更集引入了两个分支。
您的rename-threshold
方法对于您正在尝试做的事情来说是可行的。除非您的分支是永远不会合并的分叉项目,否则在分支之间定期进行书面筛选永远不是一个可持续的工作流程。如果是这样的话,请继续前进,祝你好运。如果您曾经期望将分支合并回一个有凝聚力的整体,我建议您更改代码流的方式。这里有一些很棒的资源:
-
git项目文档。
-
gitflow模型,在这些部分非常流行。
-
ProGit关于分布式工作流的章节。
分支之间的规则樱桃采摘会生成具有不同SHA1散列的相同变化集。在足够长的时间内进行足够大的扩展,跟踪代码变得很困难,了解你的历史几乎是不可能的,合并分支会让你觉得自己在M.C.Escher的画中昏倒醒来。现在不是个好时机。
根据你对这个答案的评论,你的用例听起来是一个可行的选择。在这种情况下,我建议使用一种稍微不那么费力的方法将补丁集应用于重命名的文件:
git checkout branchB
git diff <commit>~1 <commit> |
sed 's:<path_on_branchA>:<path_on_branchB>:g' |
git apply
其中<commit>
是要从branchA
移动到branchB
的提交。使用这种方法,你不会得到提交元数据,也就是说,它只会应用更改,不会提交。但你可以用sed
和git am
轻松地操作git format-patch
的输出。这取决于你想做什么。
这将省去生成临时分支、选择正确的重命名阈值和重新定基的麻烦。它永远不会像直接的git merge
那样干净,但我以前用过,一旦掌握了窍门,它就很容易了。
git rebase
使用与git merge
相同的重命名检测逻辑,甚至使用相同的选项--strategy-option="rename-threshold=30"
来控制它。
这里发生的事情是,首先将整个源分支重新绑定到目标分支上(作为sourceTemp
),然后从本质上挑选最后一个提交到target
上。顺便说一句,git rebase -onto target src~ src
与在target
上执行git cherry-pick src
相同:在src
执行一个提交并将其应用于target
。
与直接挑选相比,工作流中的变化是,您逐渐处理源分支上的提交,这可能比一步完成所有任务更容易捕获重命名。如果你直接挑选,那么必须通过观察target
和src~
之间的差异来识别重命名,这可能还有很长的路要走;当第一次对整个源分支进行重定基时,重命名是分小步骤处理的。
git checkout target
# Make an artificial merge to link target and source's parent
git merge source^ --strategy=ours
# Equivalent to cherry-pick but includes rename detection
git merge source --no-ff
# ... fix all merge conflicts and finish the merge
# Get rid of those artificial merges preserving file changes
git reset --soft HEAD~2
# Make a commit to store cherry-picked files
git commit -m "Cherry-pick source branch"
有时,文件分歧太大,因此没有一种重命名算法可以准确地确定重命名,所以我使用"hammer方法"。
git checkout target
# get list of files modified in source branch
git diff source^ source --name-only
# ... (A) rename corresponding target files to match source's ones (possibly write script to automate this)
git add -A
git commit -m "Temporarily rename files for cherry-pick purposes"
# There is no need for rename detection anymore
git cherry-pick source
# ... resolve all conflicts and finish the cherry-pick
# ... rename files back, reverting (A) step. Note that you cannot use `git revert` here
git add -A
git commit -m "Rename files back"
# Get rid of the artificial commits made but preserve file changes
git reset --soft HEAD~3
# Make a commit to store cherry-picked files
git commit -m "Cherry-pick source branch"