我想将 git 历史从新仓库“附加”到旧仓库。所以,我有 2 个存储库,
old_repository
:远程:remote_old,提交:400 次提交new_repository
:远程:remote_new,提交:200 次提交这 2 个存储库是完全不同的,并且基于具有不同遥控器的不同帐户。我已将 new
存储库的内容添加到 old
存储库。现在我还想“合并”他们的历史记录,即我想从 new
存储库中获取所有提交并将其附加到 old
存储库的提交中。
我试图寻找答案,但找不到确切的答案。我不想弄乱 old
存储库,因为它的内容用于在生产中运行,这就是为什么我想知道什么是最安全的方法。任何帮助或指导都会非常有帮助!
谢谢!
答案 0 :(得分:0)
这个问题没有简单、容易、一刀切的答案,因为对于可接受的结果,不同的人有不同的限制和品味。
这里的(或一个)根本问题是,在 Git 中,历史是存储库中的一组提交,以提交图的形式......但与此同时,提交按哈希 ID 编号,并且这些数字考虑了迄今为止的前一个提交图。
让我们做一个小插图。代替在 repo O 中的 400 次提交(对于旧的)和在 repo N 中的 200 次提交(对于新的),让我们在 4 次提交< strong>O 和 两个 N,就像这样:
A <-B <-C <-D <-- main
E <-F <-- main
显然提交A
到D
是O的内容,而E
和F
是N<的内容/strong>。
将两组提交放入任何存储库是微不足道的。这只有一个小问题,那就是 O 和 N 都使用分支名称 main
来查找“最后”提交({{1} } 和 D
分别)。但是分支名称并不重要:如果我们愿意,我们可以将它们更改为 F
和 branch-O
,并将所有这些都放在我们的 build-up-new-history 存储库中:
branch-N
解决现有问题的一个简单解决方案(将这些合并到单个历史记录中)是使用 A--B--C--D <-- branch-O
E--------F <-- branch-N
或等效物来构建合并提交 git merge
,其父项(复数形式)是提交 {{1} } 和 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 .
的哈希 ID 仍然有效,因为在合并提交 M
的组装存储库中,提交 A
仍然存在,与之前完全一样。这同样适用于所有其他现有提交。但是这种方法的坏处是这次合并的历史分叉。正如我们刚刚看到的,合并提交 M
的内容完全是任意的。不知何故,您提出了您声称的正确合并,然后将其卡入到位。如果您的正确合并确实是正确的,那很好,但是没有人可以看到您是如何想出这个正确合并的,因此没有人知道为什么它(据说)是正确的。
你提到不喜欢How do you merge two Git repositories?是因为:
<块引用>我不想创建子树
但是如果你阅读了all那里的答案,你会看到我刚刚在上面提供的一个变体,它使用 A
然后调整结果合并(而不仅仅是擦掉并更换)。这也有效;它具有我在此处概述的优点和缺点,如果您在合并提交的日志消息中描述了您必须执行的操作,您可以给未来的源代码考古学家留言,解释为什么你的合并是正确的合并。
同样,您应该返回并阅读所有另一个问题的答案,但现在,让我们转向另一个相对简单的选项——相对简单,但是毕竟,在真正的存储库中并不那么简单。此选项相当于决定 N 中的哪些提交应添加到 O 中的提交。
简单版本是将all N 提交添加到O。也就是说,我们希望 Git 从提交 M
开始,向后一步提交 git merge --allow-unrelated-histories
,然后——而不是 停止,Git 通常会这样做因为提交 F
没有父级——不知何故跳转到提交 E
:
E
三个对角线点是某种幽灵般的“使 Git 跳转轨道”操作符。火车——查看历史——从D
开始,一直移动到A--B--C--D <-- branch-O
.
.
.
E--F <-- branch-N, main
的轨道末端,但不是因为我们跑完了轨道而停下来,火车现在跳过到 F
轨道。
为了让 Git 在 one 存储库中执行此操作,我们可以将 E
与 D-C-B-A
选项一起使用。它的作用是将 git replace
提交到新的和改进的替换提交 --graft
',Git 将其以特殊名称存储在存储库中的 E
namespace:
E
当 refs/replace/
正在做它的事情时,一次显示一个提交,它首先遇到提交 A--B--C--D <-- branch-O
\
E' <-- refs/replace/<big-ugly-hash-ID>
:
E--F <-- branch-N, main
并显示它。然后它后退一步提交git log
。这一次,它注意到有一个 F
条目,其哈希 ID 为 E
(一些大而难看的随机字母和数字字符串)。正是在这一点上,Git “跳转轨道”,就像它一样:它不是查看提交 refs/replace/
,而是查看这个新的和改进的替换副本E
。这个替换提交是一个不同的提交,所以它有不同的哈希 ID; Git 通过 E
名称查找替换。现在 E'
在另一个轨道上,因此它显示提交 refs/replace/hash
而不是提交 git log
,并且与原始提交 E'
不同,提交 E
是否有父提交:E
。所以 E'
继续显示 D
,然后是 git log
,依此类推。
这具有原始解决方案的所有优点,另外还有一个优点:根本没有 Magic Merge。但是,它有一个显而易见的缺点:提交 D
和 C
的内容完全基于 F
和 E
中的内容,没有考虑任何O 和 N 之间的漂移可能引起的必要合并。如果这对您的情况来说不是问题,那很好;如果是,那就有问题了。
这还有另一个缺点:克隆此存储库不会复制 F
提交 E
。因此,在克隆中,您将看到两个独立的历史,而不是一个单一的嫁接历史。如果这是您的用例的问题,您现在必须解决这个问题。
有一个简单的解决方案:使用 refs/replace/
或其新替代品 E'
使移植物“永久”,就像它一样。这是通过复制嫁接点处和之后的所有提交来实现的。也就是说,我们从这个开始:
git filter-branch
然后我们让 Git 遍历整个图表——与 git filter-repo
的方式非常相似——将该提交复制到一个新的和改进的,或者可能与原始提交相同的提交。如果副本确实绝对地、100%、逐位相同,我们将重新使用原件。如果副本发生了任何变化,我们将使用副本。
这里的棘手部分是,当我们执行此操作时,我们首先只列出所有提交哈希 ID。该列表是:A--B--C--D <-- branch-O
\
E' <-- refs/replace/<big-ugly-hash-ID>
:
E--F <-- branch-N, main
、git log
、F
、E'
、D
、C
。然后我们将这个列表放入拓扑顺序,在这种特殊情况下,这意味着反转它:B
、A
、A
、B
、 C
和 D
。
所以,首先我们复制提交 E'
。我们对提交F
没有做任何更改,因此副本是再次提交A
。我们重复 A
、A
、B
和 C
。这些也都不会变。但是当我们去复制 D
时,我们制作的副本使用 E'
——而不是 F
——作为它的父级。所以这使得一个新的和改进的提交 E'
:
E
我们从不费心在此处复制提交 F'
,因为我们没有“看到”它(在 list-out-commit-hash-IDs 步骤中跳过了轨道)。我们不需要需要,因为我们之前复制了它,使用 A--B--C--D
\
E'-F'
:这就是 E
的初衷。
完成所有复制后,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
和 {{1} },最终会被完全丢弃。
这可能就是你想要的。它的缺点是新的和改进的替换提交 git filter-branch
和 E
与存储库 N 中的提交编号不同。如果您曾经使用这个组合存储库 C 并将它引入到一个读取 N 副本的 Git 程序中,那么您的 Git 正在查看存储库 C 会说:哦,新提交!给我一份 F
和 E'
的副本,以便我可以将它们添加到我的收藏中! 现在您将拥有从 N 保存(通过复制)的所有提交的副本,因为您将再次获得所有原件。
如果这个缺点(所有 N 次提交的重新编号)对您来说不是问题,那么这可能是您想要的方法。
您真正的问题是您必须决定您希望在新的组合存储库中拥有哪些提交集。
存储库O 和N 中的现有 提交就是它们的样子。他们将永远这样。他们将永远拥有这些哈希 ID。这就是哈希 ID 的含义:它是提交的标识。所有提交都被永久冻结。您可以进行具有不同快照和/或不同父项的新的和改进的提交;这是新历史; O 和 N 中的旧提交是 现有 历史记录。这就是全部!你可以用它做很多事情,链接问题中的(许多)答案提供了用这些历史做这些不同事情的不同方法。
由您决定您想要做什么。然后,在那里(和这里)寻找实现它的方法。