当重命名文件很常见时,替代git cherry-pick

时间:2012-08-02 12:08:18

标签: git git-rebase cherry-pick renaming git-cherry-pick

我想从分支机构到另一个分支机构挑选单个提交。 我希望文件重命名很常见,但仍然希望能够在没有人为干预的情况下应用更改。

由于内置的​​cherry-pick命令没有接缝来检测重命名(至少在我的测试用例中),特别是当与修改重命名的文件结合使用时。

我尝试了一下,最后想出了一个涉及两个rebase操作的解决方案。

假设我有一个名为 target 的分支,指向我想要应用樱桃选择的提交。 我想要挑选的提交由名为 source 的分支指向。

然后我执行以下命令:

  1. 创建分支sourceTemp,指向与源相同的提交(因为我想保留分支源)
  2. git rebase --strategy="recursive" --strategy-option="rename-threshold=30" target sourceTemp(可能使用其他阈值;测试文件非常小,因此更改相对较大)
  3. git rebase --onto target sourceTemp~ sourceTemp
  4. 这仅适用于分支中最后一次提交引入的更改为目标

    我也把测试放在github上:

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

    我想知道的是,如果这种方法是可行的,或者它只能在我简单的测试环境中工作!

    更新:替代方法

    我将补丁重新绑定到来源目标的合并基础:

    开始情况

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

    我想把D挑选到B上。

    1. 在创建新分支补丁

      后将D重新映射到M上
        A - B     <--- target
       /
      M - D'      <--- patch
       \
        C - D     <--- source
      
    2. 合并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)
      
    3. 优点是E和F现在可以合并而没有问题。 替代方法:尽可能早地在层次结构中包含补丁,这样就不会创建D而是直接创建D'并保存自己的rebase。

      以前版本的优点是你可以合并两个分支“new source”和“patched target”并且它将起作用(如果源和目标的合并当然可以工作)并且不会引入相同的变更集两次因为git知道由于合并操作将变更集引入两个分支。

4 个答案:

答案 0 :(得分:6)

您的rename-threshold方法对于您正在尝试的方法是可行的。除非您的分支机构是永远不会合并的分叉项目,否则分支机构之间的常规樱桃选择永远不是一个可持续的工作流程。如果是这样的话,那就去吧,祝你好运。如果您曾期望将您的分支合并为一个有凝聚力的整体,我建议您更改代码流的方式。以下是一些很好的资源:

  1. The git project docs.

  2. The gitflow model, quite popular around these parts.

  3. ProGit's chapter on distributed workflows.

  4. 在分支之间定期挑选樱桃,生成具有不同SHA1哈希值的相同变更集。在足够长的时间内缩放,跟踪代码变得困难,理解你的历史变得几乎不可能,并且合并分支会让你觉得你昏迷并在M.C. Escher painting.中醒来。这不是一个好时机。

    根据您对此答案的评论,您的用例听起来像是一个可行的樱桃选择。在这种情况下,我建议将补丁集应用于重命名的文件的工作量略微减少:

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

    其中<commit>是您要从branchA移至branchB的提交。使用此方法,您将无法获取提交元数据,即它只是应用更改,它不会提交它。但您可以使用git format-patchsed轻松操纵git am的输出。只取决于你想做什么。

    这将为您节省生成临时分支,选择正确的重命名阈值和重新定位的麻烦。它永远不会像直线git merge一样干净,但我之前已经使用过它,一旦掌握它就很容易。

答案 1 :(得分:1)

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

这里发生的事情是你首先将整个源分支重新绑定到你的目标分支(如sourceTemp),然后基本上挑选最后一次提交到target。顺便提一下,git rebase -onto target src~ srcgit cherry-pick src上的target相同:在src处进行一次提交并将其应用于target

您的工作流程与直接挑选的更改是您逐步处理源分支上的提交,这可能比一步完成所有操作都更容易重命名。如果您直接挑选,则必须通过查看targetsrc~之间的差异来识别重命名,这可能有很长的路要走;当第一次重新定义整个源分支时,重命名将以小步骤处理。

答案 2 :(得分: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"

答案 3 :(得分:0)

有时,文件分歧太多,因此没有一个重命名算法可以准确地确定重命名,所以我使用了#34;锤子方法&#34;。

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"