如何在保留提交历史记录的同时将一个代码块从一个git分支复制到另一个git分支?

时间:2016-10-20 22:25:09

标签: git

我正在同时处理同一个仓库中的两个功能,并且我已经意识到一个分支中的功能在另一个分支(在同一个文件中)中会很有用。第一个分支不是100%完成,所以我不想将那个合并/重新组合到第二个分支上。我真的只需要一小块代码。

显然,我可以复制和粘贴,但是这不会保留提交历史记录,我想,当我将它们合并到主服务器中时,它可能会导致合并冲突。

我需要的功能分散在多个提交中,其中还有其他更改,因此我不能轻易地挑选构成该代码块的提交。

我也不能做一个交互式的rebase并改变第一个分支的历史(让我更容易挑选我需要的一点代码),因为除了大量的代码之外,我和#39;我已经将我的代码推送到GitHub,并且在我可以合并到master之前等待代码审查,所以在我的同事正在审查我的代码时,我不应该重写历史记录。

那么,我最好的选择是什么?这种感觉就像在这种情况下我需要一些疯狂的git-fu来保持良好和干净。

我能想到的唯一另一个选择是在本地进行交互式rebase,而不是推送到GitHub,然后挑选一个包含我需要的函数的提交,然后reset我的第一个分支回到远程分支。但这感觉就像一个不太理想的解决方案,而且我不确定与简单地复制和粘贴代码真的会有很大不同。

3 个答案:

答案 0 :(得分:1)

  

显然,我可以复制并粘贴,但这不会保留提交历史...

正确。历史提交;复制代码或文件,并进行新的提交,创造了新的历史。

  

我认为,当我将它们合并为主时,它可能会导致合并冲突。

也许。幸运的是,没有。这里的关键是合并通过组合两个单独的差异来工作。你现在拥有的是这样的:

          A--B--C--D   <-- yourbranch
         /
...--o--*              <-- master, origin/master

(根据需要将master替换为branch)。在这里&#34;你的承诺&#34;是AD的标记,构成A--B--C--D链。

如果你复制(全部)你的提交,这就是你得到的:

          A--B--C--D   <-- yourbranch
         /
...--o--*              <-- master, origin/master
         \
          A'-B'-C'-D'  <-- yourcopy

现在,git merge的工作方式如下。我们假设您在master并且正在合并yourbranch

tip1=$(git rev-parse master)
tip2=$(git rev-parse yourbranch)
base=$(git merge-base $tip1 $tip2)

完成此操作后,tip2指向提交D,而tip1指向当前分支master的提示,即提交* 。同时base指向这两个提交的合并基础。这是两个分支加入的第一个提交,即...再次提交*

不以某种方式强制合并(--no-ff--ff-only)的合并将检查合并基础是否与当前提示相同(tip1或commit *) 。由于,因此该合并将变为快进,而根本不是合并。

强制--no-ff的合并继续并进行真正的合并,即使它是微不足道的。在这种情况下,我们将得到:

          A--B--C--D   <-- yourbranch
         /          \
...--o--*------------M   <-- master, origin/master
         \
          A'-B'-C'-D'  <-- yourcopy

强制--ff-only的合并仍然会检查。如果它无法执行快进,则只会失败。)

目前,我们假设我们有强制合并M

现在,假设您已复制yourcopy并再次提交E

          A--B--C--D   <-- yourbranch
         /          \
...--o--*------------M   <-- master, origin/master
         \
          A'-B'-C'-D'-E  <-- yourcopy

你现在可以向Git询问git merge yourcopy,它会做同样的事情:

tip1=$(git rev-parse master)       # commit M
tip2=$(git rev-parse yourcopy)     # commit E
base=$(git merge-base $tip1 $tip2)

masteryourcopy在哪里加入?向后关注提交,直到两个流加入:再次提交*。因此合并基础是提交*。现在我们有一个非平凡的合并,所以Git必须做两个差异:

git diff $base $tip1     # * vs M: basically, A+B+C+D as a patch
git diff $base $tip2     # A'+B'+C'+D'+E as a patch

合并代码现在尽力合并这两个补丁,每次更改 。但A+B+C+D A'+B'+C'+D'完全相同,因此这两个补丁之间的唯一区别基本上只是提交E。在&#34;好&#34;案例 - 最终令人惊讶的共同点 - 这里很少甚至没有合并冲突。

即使您只复制提交的部分,情况也是如此:

          A--B--C--D   <-- yourbranch
         /          \
...--o--*------------M   <-- master, origin/master
         \
          B'-D'-E        <-- yourcopy

一侧&#34;差异&#34;基本上是A+B+C+D,而另一方面的差异是#{1}}。 B'+D'+E只是B+D+E。 Git可能(并且通常)注意到B'D'已经在那里,并且只进行了E的更改。

即使原始&#34;合并&#34;所有这一切仍然是正确的。是一个快进的人。我们只是将中间提交图重绘为:

          A--B--C--D   <-- yourbranch, master, origin/master
         /
...--o--*
         \
          A'-B'-C'-D'-E  <-- yourcopy

合并基础仍为*,一切正常。如果您现在进行合并(当前分支设置为master),则得到:

          A--B--C--D        <-- yourbranch, origin/master
         /          \
...--o--*            ---M   <-- master
         \             /
          A'-B'-C'-D'-E     <-- yourcopy

如果您在合并A'-B'-C'-D'-E上进行合并M2,则会得到一个类似的图表,其中包含M序列。在这两种情况下,唯一的烦恼是复制的提交保留在存储库中,因此当您运行git log时,您会看到它们两次。

以上太简单了

当然,在这种情况下,您只需在E上添加D

                     E   <-- feature2
                    /
          A--B--C--D   <-- yourbranch
         /
...--o--*              <-- master, origin/master

这将是要走的路:没有复制的提交,一切都很好(虽然feature2现在取决于yourbranch)。

你真正拥有的东西看起来更像是这样,毫无疑问:

          A--B--C--D   <-- yourbranch
         /
...--o--*              <-- master, origin/master
         \
          E--F--G      <-- feature2

您现在已经注意到,您希望A-B-C-D中的feature2序列中的部分或全部代码,,就好像您已重新定位feature2顶部yourbranch。但是,您可以再次git cherry-pick尽可能多地进行A'B'C'和/或D'提交。您可以根据需要对这些提交进行重组(rebase -i并重新组织),但我们只需将BD添加为副本即可:

          A--B--C--D   <-- yourbranch
         /
...--o--*              <-- master, origin/master
         \
          E--F--G--B'-D'   <-- feature2

和以前一样,合并将尝试不复制代码(尽管来自额外提交副本的历史将显示)。但是,让我们说你以后会在你的(合并)feature2 - 通过 - A上面的 rebase D获得批准。当你这样做时会发生什么更好,因为git rebase - 通过复制提交工作,与git cherry-pick相同 - 实际上搜索已经复制的提交并将其保留。因此,让我们说masterorigin/master快速转发以包含D,或D合并进来 - 无论发生什么都没关系,只要提交AD现在全部 on master(即使是通过合并提交):

          A--B--C--D      <-- yourbranch
         /          \
...--o--*------------M    <-- master, origin/master
         \
          E--F--G--B'-D'  <-- feature2

现在您结帐feature2并运行git rebase master。你的git找到了提交E-F-G-B'-D'并设置为复制它们,但是当它复制时,它会检查:这个提交已经在那里了吗?此检查不是通过提交哈希 - 此检查将失败 - 而是通过修补程序哈希(有关详细信息,请参阅the git patch-id documentation)。

由于B'B的副本(因此具有相同的修补程序ID),D'D的副本,rebase份只有E-F-G,给予:

          A--B--C--D             <-- yourbranch
         /          \
...--o--*------------M           <-- master, origin/master
         \            \
          \            E'-F'-G'  <-- feature2
           \
            E--F--G--B'-D'       [old feature2, now abandoned]

掩盖&#34;放弃&#34;提交(用你的手阻挡它们,例如:-))看起来Git已经神奇地完成了你想要的东西。 (它并不完全是魔术 - 它只是补丁ID - 但它可能就是你想要的。当然,如果你被迫重做B和/或D ,最终进入的重新制作的作品可能不再与你制作的副本相匹配,然后你会真正看到缺乏魔法。)

答案 1 :(得分:1)

如果在另一个答案中更改原始版本是不合适的,则可以将该函数复制并粘贴到具有不同名称的文件中,以便将来合并时不会发生复杂的冲突。您只需要删除临时文件。根据您的语言,由于重复的定义,您可能会因编译错误而注意到它。

答案 2 :(得分:0)

将该函数的实现提取到一个单独的分支中,将其合并到集成分支中,然后在最新的集成分支之上进行rebase。现在,在与最初实现该功能的分支进行一些冲突解决后,您现在可以在两个分支中使用该功能。