如何将一个孤立的分支“原样”附加到主分支?

4
在转向使用Git的过程中,我们已经将一个解决方案的生产版本作为“master”提交。然后,我们采取了开发版本,并创建了一个名为“develop”的孤立分支。背景:我们有点混乱的原因是,从开发版本到生产版本没有干净的演变。而且组装所涉及的解决方案的复杂性使我们想避免放弃存储库并重试。最终,我们只想将这些版本放入Git中,并在Git中开始清理。
因此 - 现在我们认为最好从“master”分支分支开发版本,而不是将其保持为孤立分支。
我们如何基本地将“master”提交作为“develop”提交的父提交,但不进行任何合并?而不改变第一个“develop”提交的文件内容?
也就是说 - 只是以某种方式接枝“develop”,完全相同,如果有意义的话?

git rebase --onto master develop - siride
之前尝试过一些操作,我认为变基(rebasing)总是尝试合并,这个想法是错误的吗?"--onto" 可以阻止这种情况发生吗? - El Entrenador
重新定位不会合并,本质上来说。然而,如果重新定位的分支中的提交引入了与已在重新定位到的分支中进行的更改冲突的更改,则会出现合并冲突。从原则上讲,这是不可避免的。对于您的情况,我只能建议您尝试一下,并希望您没有冲突。 - siride
1个回答

1

看起来你想修改你的提交图(commit graph),而不修改附加到这些提交的任何树(trees)。

如果你理解git内部工作原理,这个语句就会更有意义。关键在于:

  1. 所有的提交都是永久且不可更改的,因为提交(或者实际上Git的四个内部对象之一)的“真名”是其内容的SHA-1加密校验和。这意味着,如果您尝试更改任何内容(或失败的磁盘驱动器更改了某些内容),Git将会抱怨校验和错误,因为每个对象中看到的内容不再与其“真名”匹配。

  2. 每个提交还携带一个“tree”对象的SHA-1 ID,该对象充当与该提交相关联的源代码的完整快照。(树给出每个文件或目录的文件名和SHA-1“真名”,子目录由另一个树来表示。在这里细节并不太重要。)

  3. 每个提交还列出了其父项的“真名”SHA-1 IDs。因此,给定develop的尖端提交,Git可以读取该提交并找到其直接父项。读取该父节点(或这些父节点),Git会找到下一个ID(S),并读取其父亲,依此类推。此过程在读取“根”提交时停止,即没有父节点的提交。执行--orphan检出,然后进行提交会导致一个新的根提交,这无疑是您创建分支的方式。

这些父ID和树ID存储在任何给定的提交中,被称为"指向"Git存储库中的其他对象。 (存储库中只有四种类型的对象。我们已经提到了"提交"和"树",另外两个是"blob"——这是Git保存文件的方式——和"tag"。树指向blob和子树,而"tag"对象用于带注释的标签。)


因此,您想要做的是更改develop分支的根提交,以便它现在具有父提交,特别是从当前master分支的末尾可达的某个提交。(也许您想要末尾本身,也许您想要像master〜100这样的东西:当您进行更改时,此细节仅很重要。)
坏消息是,由于项目#1的原因,您不能完全这样做。
好消息是,您可以使用三种替代方法之一来完成此操作(然后如果需要和需要,可以使移植永久化)。
首先,git有一个称为“移植”的东西。 它们的工作效果不太好,因此git有一个新的更好的东西叫做“替换”。 根据您的git版本,您应该至少拥有一个,几乎肯定两者都有。
这两者都采用了相同的基本思路。在 git 执行其图形遍历,从提交到父级时,你希望在某个点上能让 git 更改其遍历路径。使用 git 梗概,只需指定当有一个 ID 为 <X> 的对象时,它应遍历到父级 <Y>,这样就可以找到当前 develop 分支上的根提交,并将其直接嫁接到 master 分支的某个提交上。
这些梗概不会被 git clone 复制,并引入其他问题,因此现在 git 有了git replace。它在存储库中创建了一个实际对象,允许您检查提交图形及其替换。要在此情况下使用它,您需要为根提交创建替换提交,该提交与现有根提交完全相同,只是具有所需的父级。
如果您想完全重写历史,使得所有用户在经过一次痛苦的重写步骤后都能轻松使用,那么您可以设置一个嫁接点,然后使用git filter-branch来复制和替换嫁接点。或者,也许稍微容易一些的方法是指定一个父筛选器(而没有其他筛选器)。有关详细信息,请参见 git filter-branch文档;它有一个非常相似的示例。(文档建议创建嫁接点更简单,但我认为使用--parent-filter更简单;但无论哪种方式,都有一个方便的示例。)
请注意,filter-branch会复制每个“筛选”提交,并应用一些更改,通过(虚拟)检出该提交,然后应用筛选器,然后从结果中创建一个新的提交。在这种情况下,第一个更改是向根提交添加父ID。第二个更改是filter-branch自动执行的:使跟随不再孤立的根复制点的提交指向根复制。第三个更改与第二个相同,依此类推:
      A--B--C     <-- develop (before filtering)

...--o--o--...    <-- master
      \
       A'-B'-C'   <-- develop (after filtering)

在这里,提交A'与提交A完全相同,只是它有一个父ID;提交B'与提交B完全相同,只是它的父代是A'而不是A;等等。
(顺便说一下,这里所有的父级箭头都指向左边,但是filter-branch从左到右工作。它通过首先枚举要过滤的所有提交,以从右到左的方式获取它们的ID,然后从左到右进行过滤。) 这个现有的SO问答中有更多关于使用嫁接或替换的内容。 我上面确实提到了第三种方法。这种方法仅在develop没有现有合并提交时才有效。您可以像siride建议的那样,将develop中的每个提交简单地变基到目标提交上。您需要使用--root运行该变基命令,以从独立的develop分支的根部复制每个提交。
这样做是因为git rebase只是复制提交,就像git filter-branch一样。从某种意义上说,这更加费力,因为重新应用差异(使用重复的git cherry-pick)而不是仅保留每个复制提交的现有“树”对象的方式来复制提交,但它比git filter-branch容易得多。这里的缺点是rebase根本不处理合并提交(好吧,除非您使用--preserve-merges,这会使用交互式变基代码)。

感谢您的帮助。尝试了git rebase,但出现了合并冲突。也许您的--parent-filter方法会起作用。但是,如果按照您提供的链接到其他SO问题,我发现了这个“git reparent”脚本 - 看起来应该可以实现我们想要的功能:https://github.com/MarkLodato/git-reparent/tree/e886dc7e970f8c05ee7b82a6f8d855ffb7cc4d88 - El Entrenador
根据文档,该脚本仅复制“HEAD”提交(这很容易,但如果您想要复制整个提交链,则无济于事)。 - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接