2

I'd like to cherry-pick single commits from on branch to another. I expect file renames to be quite common but still want to be able to apply changes without human intervention.

Since the built-in cherry-pick command doesn't seam to detect renames (at least in my test cases), especially when combined with modification to the renamed file.

I tried a bit and finally came up with a solution involving two rebase operations.

Let's assume I have a branch named target pointing at the commit I want to apply the cherry-pick to. The commit I want to cherry-pick is pointed to by a branch named source.

I then execute the following commands:

  1. create branch sourceTemp pointing at the same commit as source (since I want to retain the branch source )
  2. git rebase --strategy="recursive" --strategy-option="rename-threshold=30" target sourceTemp (maybe use another threshold; the test file was quite small and the changes therefore relatively large)
  3. git rebase --onto target sourceTemp~ sourceTemp

This applies only the changes introduced by the last commit in branch source to target.

I also put my test on github:

https://github.com/fraschfn/cherry-pick

What I like to know is, if this approach is feasible or if it only worked in my simple test setting!

Update: Alternative method

I rebase the patch to the merge base of source and target:

start situation

    A - B     <--- target
   /
  M 
   \
    C - D     <--- source

I want to cherry-pick D onto B.

  1. rebase D onto M after creating a new branch patch

      A - B     <--- target
     /
    M - D'      <--- patch
     \
      C - D     <--- source
    
  2. merge C and D' to obtain a replacement for source

    merge B and D' to obtain the patched version of target

       A - B    <--- target
      /     \
     /      E   <--- patched target
    /      / 
    M -  D'     <--- patch
     \    \
      \   F     <--- new source (same snapshot as source different history)
       \ /
        C - D   <--- source (will be discarded)
    

The advantage is that E and F can now be merged without a problem. Alternative way: Include the patch as early as possible in the hierarchy thus not creating D but directly D' and saving yourself the rebase.

The advantage above the previous version is that you can merge the two branches "new source" and "patched target" and it will work (if the merge of source and target would work of course) and not introduce the same changeset twice since git knows due to the merge operation which introduced the changeset into both branches.

4

4 回答 4

7

您的rename-threshold方法对于您正在尝试做的事情是可行的。分支之间的书面、定期挑选绝不是可持续的工作流程,除非您的分支是永远不会合并的分叉项目。如果是这样的话,那就去吧,祝你好运。如果您期望将分支合并回一个有凝聚力的整体,我建议您更改代码流动方式。这里有一些很好的资源:

  1. git 项目文档。

  2. gitflow 模型,在这些部分非常流行。

  3. ProGit 关于分布式工作流的章节。

分支之间的定期挑选会生成具有不同 SHA1 哈希的相同更改集。在足够长的时间段内进行足够的扩展,跟踪代码变得困难,几乎不可能理解你的历史,合并分支让你感觉就像你在MC Escher 的画中昏迷并醒来。这不是一个好时机。

根据您对此答案的评论,您的用例听起来像是一个可行的选择。在这种情况下,我建议将补丁集应用于重命名文件的劳动强度稍低的方法:

git checkout branchB
git diff <commit>~1 <commit> |
    sed 's:<path_on_branchA>:<path_on_branchB>:g' |
    git apply

你想从哪里<commit>移到的branchA提交branchB。使用这种方法,您不会获得提交元数据,即它只会应用更改,不会提交。但是您可以轻松地操纵git format-patchwithsed和的输出git am。只取决于你想做什么。

这将为您省去生成临时分支、选择正确的重命名阈值和变基的麻烦。它永远不会像直线上升那样干净git merge,但我以前用过它,一旦你掌握了它就很容易了。

于 2012-08-02T12:47:42.543 回答
2

git rebase使用相同的重命名检测逻辑git merge,甚至使用相同的选项--strategy-option="rename-threshold=30"来控制它。

这里发生的事情是,您首先将整个源分支重新定位到您的目标分支(as sourceTemp),然后基本上将其上的最后一次提交选择到target. 顺便说一句,这和ongit rebase -onto target src~ src是一样的:将一个 commit at应用到.git cherry-pick srctargetsrctarget

与直接挑选樱桃相比,您的工作流程的变化在于您逐渐处理源分支上的提交,这可能比一步完成更容易捕获重命名。如果您直接挑选,则必须通过查看和之间的区别来识别重命名targetsrc~这可能是很长的路要走;当第一次重新定位整个源分支时,重命名是分小步处理的。

于 2014-03-28T13:02:17.310 回答
0

有时,文件分歧太大,所以没有一个重命名算法可以准确地确定重命名,所以我使用“锤子方法”。

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"
于 2015-10-17T13:15:53.340 回答
0
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"
于 2015-10-17T10:35:49.623 回答