繁体   English   中英

将 git 历史记录从新存储库附加到旧存储库

[英]Appending git history from a new repository to an old one

我想将 git 历史从新仓库“附加”到旧仓库。 所以,我有2个存储库,

  • old_repository :远程:remote_old,提交:400 次提交
  • new_repository :远程:remote_new,提交:200 次提交

这两个存储库完全不同,并且基于具有不同遥控器的不同帐户。 我已将new存储库的内容添加到old存储库。 现在我还想“合并”他们的历史,即我想new存储库中获取所有提交,并将 append 提交到old存储库中。

我试图寻找答案,但找不到确定的答案。 我不想弄乱old存储库,因为它的内容用于在生产环境中运行,这就是为什么我想知道最安全的方法是什么。 任何帮助或方向都会非常有帮助!

谢谢!

对于这个问题,没有简单、容易、一刀切的答案,因为不同的人对于什么样的结果可以接受有不同的限制和品味。

这里的(或一个)根本问题是,在 Git 中,历史存储库中的一组提交,以提交图的形式......但同时,提交由 hash ID 编号,数字取考虑到之前的提交图。

让我们做一个小插图。 与其在 repo O (旧)中提交 400 次,在 repo N (对于新)中提交 200 次,不如在O中只有四个提交,在N中只有两个,如下所示:

A <-B <-C <-D   <-- main

E <-F   <-- main

显然,提交ADO的内容,而EFN的内容。

将两组提交都放入任何存储库是微不足道的。 这只有一个小问题,那就是ON都使用分支名称main来查找“最后”提交(分别是DF )。 但是分支名称并不重要:如果我们愿意,我们可以将它们更改为branch-Obranch-N ,并将所有这些都放在我们的 build-up-new-history 存储库中:

A--B--C--D   <-- branch-O

E--------F   <-- branch-N

您现有问题的一个简单解决方案(将它们组合成一个历史记录)是使用git merge或等效构建一个合并提交M ,其父复数是提交DF ,按任一顺序:

A--B--C--D   <-- branch-O
          \
           M   <-- main
          /
E--------F   <-- branch-N

提交M快照由您决定。 你可以从头开始构建它,或者做任何你喜欢的事情。 例如:

git branch main branch-O
git switch main
git merge --no-commit --allow-unrelated-histories branch-N
git rm -rf .
cp -R /tmp/already-prepared-files/* .
git add .
git commit

将构建一个新的合并提交,其内容是准备好的文件。 git rm -rf. 步骤消除了git merge为合并文件所做的所有工作,包括git merge遇到的所有冲突。 cp -R步骤将准备好的文件放到此处,并git add. 步骤将它们添加为新合并提交M的内容。 (有更优雅的方法可以做到这一点:上面是一种简单的、蛮力的核武器和铺路方法,旨在易于理解。)

这种方法的好处是,所有现有存储库中的所有现有提交号都被保留 提交A的 hash ID 仍然有效,因为在合并提交M的组装存储库中,提交A仍然存在,与以前完全相同。 所有其他现有提交也是如此。 但是这种方法的坏处是历史在这次合并中分叉了。 正如我们刚刚看到的,合并提交M内容是完全任意的。 不知何故,您想出了您声称的正确合并,然后将其卡在了适当的位置。 如果您的正确合并是真正正确的,那很好,但是没有人可以看到您是如何提出这个正确合并的,因此没有人可以看到为什么它(据说)是正确的。

您提到不喜欢如何合并两个 Git 存储库? 因为:

我不想创建子树

但是,如果您在那里阅读所有答案,您会看到我刚刚在上面提供的那个变体,它使用git merge --allow-unrelated-histories然后调整生成的合并(而不是仅仅将其清除并替换它)。 这也行得通; 它有我在这里概述的优点和缺点,如果你在合并提交的日志消息中描述了你必须做的事情,你可以给未来的源代码考古学家留言,解释为什么你的合并是正确的合并.

同样,您应该返回 go 并阅读其他问题的所有答案,但是现在,让我们继续另一个相对简单的选项 -相对简单,但毕竟不是那么简单,在真实的存储库中。 此选项相当于决定应将N中的哪些提交添加到O中的提交中。

简单的版本是将所有N提交添加到O 也就是说,我们希望 Git 从提交F开始,向后工作一步提交E ,然后——而不是停止Git 通常会这样做,因为提交E没有父项——以某种方式跳转到提交D

A--B--C--D   <-- branch-O
          .
           .
            .
             E--F   <-- branch-N, main

其中三个对角线点是某种幽灵般的“制作 Git 跳转轨道”运算符。 火车——历史的观察——从F开始,一直移动到E的轨道尽头,但不是因为我们跑出轨道而停下来,而是现在跳到DCBA轨道。

要使 Git 在一个存储库中执行此操作,我们可以使用git replace --graft选项。 这样做是将提交E复制到一个新的和改进的替换提交E ',它 Git 以refs/replace/命名空间中的特殊名称存储在存储库中:

A--B--C--D   <-- branch-O
          \
           E'   <-- refs/replace/<big-ugly-hash-ID>
           :
           E--F   <-- branch-N, main

git log正在做它的事情时,一次显示一个提交,它首先遇到提交F并显示它。 然后它后退一步提交E 这一次,它注意到有一个refs/replace/条目,其 hash ID 为E (一些大而丑陋的随机字母和数字字符串)。 正是在这一点上,Git “跳轨”,就像是:它不是查看提交E ,而是查看这个新的和改进的替换副本E' 这个替换提交是不同的提交,所以它有不同的 hash ID; Git 通过refs/replace/ hash名称找到替换。 现在git log位于另一个轨道上,因此它显示提交E'而不是提交E ,并且与原始提交E不同,提交E'确实有一个父提交: D 所以git log继续显示D ,然后C ,依此类推。

这具有原始解决方案的所有优点,再加上一个:根本没有 Magic Merge。 但是,它有一个明显的缺点:提交FE的内容完全基于FE中的内容,而没有考虑到可能因ON之间的漂移而产生的任何必要合并。 如果这对您的情况没有问题,那很好; 如果是,那就有问题了。

这还有另一个缺点:克隆此存储库不会复制refs/replace/ commit E' 所以在一个克隆中,你会看到两个独立的历史,而不是一个单一的移植历史。 如果这对您的用例来说是个问题,那么您现在必须解决这个问题。

有一个简单的解决方案:使用git filter-branch或它的新替代品git filter-repo ,使嫁接“永久”,就像它一样。 这通过复制嫁接点处和之后的所有提交来工作 也就是说,我们从这个开始:

A--B--C--D   <-- branch-O
          \
           E'   <-- refs/replace/<big-ugly-hash-ID>
           :
           E--F   <-- branch-N, main

然后我们让 Git 遍历整个图表——与git log的方式非常相似——将提交复制到一个新的和改进的,或者可能与原始提交相同。 如果副本真的绝对的,100%,位对位相同,我们重新使用原件。 如果副本发生任何变化,我们将使用该副本。

这里棘手的部分是,当我们执行此遍历时,我们首先列出所有提交 hash ID。 该列表如下: FE'DCBA 然后我们将这个列表放入拓扑 order中,在这种特殊情况下只是意味着反转它: ABCDE'F

所以,首先我们复制提交A 我们不对提交A进行任何更改,因此副本再次提交A 我们重复BCDE' 这些也都不会改变。 但是当我们 go 复制F时,我们制作的副本使用E' - 而不是E - 作为其父级。 所以这会产生一个新的和改进的提交F'

A--B--C--D
          \
           E'-F'

我们从不费心在这里复制提交E ,因为我们没有“看到”它(在 list-out-commit-hash-IDs 步骤中跳过了轨道)。 我们不需要,因为我们之前复制了它,使用git graft :这就是E'最初的原因。

完成所有复制后, git filter-branchgit filter-repo现在将使用名称branch-Obranch-N等 — 并让它们指向复制的结果 由于branch-O曾经指向D并且复制D的结果是D ,所以branch-O仍然指向D

A--B--C--D   <-- branch-O
          \
           E'-F'

然而,名称branch-Nmain曾经指向F 复制F的结果是F'所以这两个名字现在被移动,指向F'

A--B--C--D   <-- branch-O
          \
           E'-F'  <-- branch-N, main

提交EF会在存储库中保留一段时间,但现在是无用的垃圾。 它们不会被git clone ,一旦我们清理——如果我们使用git filter-branch ,你必须调用一个手动清理步骤——这两个原件EF最终将被完全丢弃。

这可能是你想要的。 它的缺点是新的和改进的替换提交E'F'与 repo N中的提交号不同 If you ever take this combined repo C and introduce it to a Git program reading a copy of N , your Git looking at repo C will say: Ooh, new commits! 给我一份EF的副本,这样我就可以将它们添加到我的收藏中! 现在您将拥有从N保存(通过复制)的所有提交的副本,因为您将再次获得所有原件。

如果这个缺点(所有N次提交的重新编号)对您来说不是问题,那么这可能是您想要的方法。

结论

您真正的问题是您必须决定要在新的组合存储库中拥有哪些提交集

存储库ON中的现有提交就是它们的方式。 他们将永远如此。 他们将永远拥有那些 hash ID。 这就是 hash ID 的含义它是提交的标识。 所有提交都被永久冻结。 您可以进行具有不同快照和/或不同父级的新提交和改进提交; 这是新的历史; ON中的旧提交是现有历史。 就是这样,你可以用它做很多事情。 并且链接问题中的(许多)答案提供了用这些历史做这些不同事情的不同方法。

由你决定你想要做什么 然后,在那里(和这里)寻找实现它的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM