[英]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
显然,提交A
到D
是O的内容,而E
和F
是N的内容。
将两组提交都放入任何存储库是微不足道的。 这只有一个小问题,那就是O和N都使用分支名称main
来查找“最后”提交(分别是D
和F
)。 但是分支名称并不重要:如果我们愿意,我们可以将它们更改为branch-O
和branch-N
,并将所有这些都放在我们的 build-up-new-history 存储库中:
A--B--C--D <-- branch-O
E--------F <-- branch-N
您现有问题的一个简单解决方案(将它们组合成一个历史记录)是使用git merge
或等效构建一个合并提交M
,其父复数是提交D
和F
,按任一顺序:
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。 但是,它有一个明显的缺点:提交F
和E
的内容完全基于F
和E
中的内容,而没有考虑到可能因O和N之间的漂移而产生的任何必要合并。 如果这对您的情况没有问题,那很好; 如果是,那就有问题了。
这还有另一个缺点:克隆此存储库不会复制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。 该列表如下: F
, E'
, D
, C
, B
, A
。 然后我们将这个列表放入拓扑 order中,在这种特殊情况下只是意味着反转它: A
, B
, C
, D
, E'
和F
。
所以,首先我们复制提交A
。 我们不对提交A
进行任何更改,因此副本再次是提交A
我们重复B
、 C
、 D
和E'
。 这些也都不会改变。 但是当我们 go 复制F
时,我们制作的副本使用E'
- 而不是E
- 作为其父级。 所以这会产生一个新的和改进的提交F'
:
A--B--C--D
\
E'-F'
我们从不费心在这里复制提交E
,因为我们没有“看到”它(在 list-out-commit-hash-IDs 步骤中跳过了轨道)。 我们不需要,因为我们之前复制了它,使用git graft
:这就是E'
最初的原因。
完成所有复制后, git filter-branch
或git filter-repo
现在将使用名称— branch-O
、 branch-N
等 — 并让它们指向复制的结果。 由于branch-O
曾经指向D
并且复制D
的结果是D
,所以branch-O
仍然指向D
:
A--B--C--D <-- branch-O
\
E'-F'
然而,名称branch-N
和main
曾经指向F
。 复制F
的结果是F'
所以这两个名字现在被移动,指向F'
:
A--B--C--D <-- branch-O
\
E'-F' <-- branch-N, main
提交E
和F
会在存储库中保留一段时间,但现在是无用的垃圾。 它们不会被git clone
,一旦我们清理——如果我们使用git filter-branch
,你必须调用一个手动清理步骤——这两个原件E
和F
最终将被完全丢弃。
这可能是你想要的。 它的缺点是新的和改进的替换提交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! 给我一份E
和F
的副本,这样我就可以将它们添加到我的收藏中! 现在您将拥有从N保存(通过复制)的所有提交的副本,因为您将再次获得所有原件。
如果这个缺点(所有N次提交的重新编号)对您来说不是问题,那么这可能是您想要的方法。
您真正的问题是您必须决定要在新的组合存储库中拥有哪些提交集。
存储库O和N中的现有提交就是它们的方式。 他们将永远如此。 他们将永远拥有那些 hash ID。 这就是 hash ID 的含义:它是提交的标识。 所有提交都被永久冻结。 您可以进行具有不同快照和/或不同父级的新提交和改进提交; 这是新的历史; O和N中的旧提交是现有历史。 就是这样,你可以用它做很多事情。 并且链接问题中的(许多)答案提供了用这些历史做这些不同事情的不同方法。
由你决定你想要做什么。 然后,在那里(和这里)寻找实现它的方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.