繁体   English   中英

Git:如何从另一个存储库导入提交并取消压缩它们?

[英]Git: how to import commits from another repository and unsquash them?

由于一位同事在分叉存储库时出现设置错误(他决定将所有文件复制粘贴到一个新的存储库中并将它们原始提交,而不是实际分叉),我现在处于需要的情况找到一种从源存储库“导入” 1100 多个提交的方法,最好不要丢失之后的提交历史记录(但如果最坏的情况发生在最坏的情况下,我可以将提交压缩到它之外 - 宁愿它是最后的手段) .

切换点是单个提交(初始 state 提交),并且在差异方面与原始提交一致。

有没有办法在初始提交与 rest 有共同父级的情况下实际做到这一点?

不重写新存储库的历史是不可能的,从而破坏了新存储库的用户,例如已经分叉了存储库并在自己的分支上工作。 头部提交的 hash 包括存储库的整个历史记录,因此如果将“旧”历史记录放在前面,则需要在之后重写所有提交,从而更改它们的 hash。

但是,您可以将旧历史记录移植到新存储库中,或者更好的是,您可以使用git replace

另一方面,如果您愿意放弃新存储库,那么只需在旧存储库上重放提交即可。 你可以用最愚蠢的方式来做这件事,就是在旧的 repo 上逐个提交地检查新的 repo,获取消息、作者姓名和日期、提交者姓名和日期,然后提交结果,然后重复.

导入提交很容易:只需使用git fetch Each commit has its own unique ID and your Git and the other Git you're talking to (via git fetch ) will tell each other their IDs, so that your Git can get any commits they have that you don't.

困难的部分是将没有关系的提交串在一起:如果他们的提交没有按编号引用您的提交,反之亦然,那么您的提交和他们的提交是不相关的。

详细信息,从 fetch 开始

我现在处于一种情况,我需要找到一种方法从源存储库“导入”1100 多个提交,最好不要丢失提交历史......

提交历史只是提交。 每个提交都有自己唯一的 ID(我知道我已经说过了,但这很重要),每个提交都通过 ID 号引用一些较早的提交。

如果我们使用大写字母来代表实际提交 hash ID 编号,我们会得到如下所示的图片:

A <-B <-C ... <-G <-H   <--main

在这里,您的 Git 使用您的分支名称main来查找链中最后一个提交的实际 hash ID,我们称之为H 提交H指回更早的提交G ,后者指回另一个更早的提交,依此类推。 在您的存储库中只有八个提交,这个向后看的链,由 Git 从最后开始并向后工作,在提交A处结束(开始?)。

如果这八个提交是从与其他一些提交系列相同的源快照创建的,但没有实际使用来自其他 Git 的hash ID ,那么其他一些 Z0BCC70105AD279503E31FE7B3F47B665 存储库有,不相关的链

K <-L <-M ... <-P   <--somebranch

因此他们的Git 存储库使用名称somebranch来查找其链中的最后一个提交; 那是提交P ,它指向之前的提交,一直持续到他们的链在他们的第一次提交时开始/结束,我们称之为K (没有明显原因跳过了IJ )。

听起来您是在说P中的快照A中的快照相匹配。 很容易判断是否是这种情况,因为您可以通过让 Git 从上一次提交 ( H ) 向后移动到第一次来找到您的第一次提交。 然后,在您的剪切和粘贴缓冲区中使用该提交 hash ID,您运行:

git remote add xyzzy <url-for-their-git>

(在这里选择一些比xyzzy更有意义的名称),然后运行:

git fetch xyzzy
git diff <hash-of-A> xyzzy/somebranch

xyzzysomebranch替换为找到提交P的适当名称。 请记住,您的 Git 在git fetch期间获得了他们的提交,包括在其链末尾的提交P 然后您的 Git 将它们的名称somebranch复制到您的xyzzy/somebranch远程跟踪名称,以便在存储库中轻松找到提交P 因此,上面的git diff将您第一次提交中的快照与他们最后一次提交中的快照进行比较。

(即使快照不同,这也不是致命的。但是,在这种情况下,您可能希望对事情进行一些不同的处理:也许,在他们的存储库中找到一些匹配的 pre P提交,或者稍后补偿这里的任何不同之处,如果这是适当和合理的。)

但问题是,在这个git fetch之后,这两个链是不相关的。 你有:

A--B--...--H   <-- main

K--L--...--P   <-- xyzzy/somebranch

在您的存储库中。 因为 Git 提交 hash 每个提交的 ID 都是真正唯一的,并且任何提交的任何部分都不能更改,因此您实际上无法将这些链连接在一起。

将它们连接在一起

我只是说你不能把它们联系在一起,这确实是真的,但是你可以做两件事。 一个涉及提出一种临时替代品,即使您将其永久化,您也可能想要这样做。 另一个涉及提出永久性替代品。

Git 作为一般概念支持进行一种后备替换提交的想法。 (实际上,您可以对任何内部 Git object 执行此操作,但在这里提交是您想要的。)您使用git replace命令来构建这些。 他们创建了一个对象——在本例中是一个新的提交对象——并向 Git 写了一种辅助指令,上面写着:如果你要使用提交X ,请改用另一个提交X' 所以我们选择你现有的提交之一——也许B ,如果AP完全匹配,所以我们根本不需要保留A ——并告诉 Git:当你要使用提交B时,使用提交B'而是

我们要做的是构造一个提交B' ,它看起来非常B ,只是有一个变化。 我们选择P而不是B'的父母是A 结果如下所示:

          A--B--C--...--H   <-- main
             :
             B'   <-- refs/replace/<hash-of-B>   # special name
            /
K--L--...--P   <-- xyzzy/somebranch

现在,当我们要求您的 Git 从H开始并向后工作时,会发生以下情况:

  • 显示/使用提交H
  • 显示/使用提交G
  • ...
  • 显示/使用提交C
  • 显示/使用提交WHOOP! 哎呀! 更换警报! 切换到B'
  • 显示/使用提交P
  • 显示/使用提交O
  • ...

现在看来,提交似乎K开始,向上运行并在H结束。

他们没有——只有这个时髦的替代品——但 Git 的大部分都服从替代品。 git log命令可以检测换出并包含一个标记,并且可以通过运行git --no-replace-objects command告诉任何Git 命令忽略替换。 但大多数情况下,大多数 Git 命令服从替换。

请注意,如果AP不匹配,源代码快照明智,您将需要使用git replaceA替换为A' ,而不是将B替换为B' 无论哪种情况,您都希望git replace --graft 有关详细信息,请参阅git replace文档

替换不会克隆

替换技巧的缺点是git clone和类似操作往往会完全忽略替换。 这就是为什么我说大多数Git 都服从它:克隆故意不服从。 也有一些方法可以克隆替换,在初始克隆之后手动克隆,但它们使用起来很笨重,你可能不想这样做。

使替换永久化

Once you have the replacement in place, and git log and other operations all look good, you can have Git "rewrite history", using the old git filter-branch or the git filter-repo . 只需让他们在不更改任何内容的情况下进行重写,无需禁用替换。 这些操作(filter-branch 或 filter-repo)将提交复制到新的和可能改进的提交。 如果并且当新副本与原始副本逐位相同时,它将获得与原始副本相同的编号,因此我们在这里通过P调用K并通过xyzzy/somebranch查找的提交不会改变。 1

因为它必须,所以这个复制操作继续进行,不像大多数Git Git 的大部分内容都是从头到尾反向工作的。 甚至 filter-branch / filter-repo 也必须向后列出提交; 然后他们确保从列表中向后复制,以便向前复制。

现在,请注意,在复制提交B (或A )时,您的 Git 将 go 输入并复制B' (或A' )。 它将保留所有数据,因为这里也没有任何变化。 但在那之后, Git 将复制C (或B )——但这一次,新副本级遵循替换规则。 所以父级是B' (或A' )替换: C (或B )变为C' (或B' )。 然后对每个后续提交重复此操作。 最终结果是这样的,假设B'B的替代品:

          A--B--C--...--H
             
             B'-C'-...'-H'
            /
K--L--...--P

这些过滤器操作之一(branch 或 repo)的最后一步是获取分支名称2并使它们指向相应的副本 所以xyzzy/somebranch仍然指向P ,因为P P但名称main现在指向H' ,因为那是H的新副本:

          A--B--C--...--H   ???
             
             B'-C'-...'-H'  <-- main
            /
K--L--...--P   <-- xyzzy/somebranch

提交H不再可找到 3因此,您暂时不会再看到它,最终,Git 会注意到它不仅没有被使用,而且无法使用。 然后 Git 将(最终)将其完全删除,您将获得按照您想要的方式连接的历史记录。


1至少,这是目标。 过滤器分支中有一个错误,它曾经在 MacOS 上出现过。 它已被修复。 可能还会出现其他错误。 如果这些 hash ID确实发生了变化,那么您发现了一个新错误。

2使用git filter-branch ,如果您有标签名称,添加--tag-name-filter cat以使其也更新标签名称很重要。 我还没有git filter-repo实际经验,但它可能对此应该更明智。 filter-branch 命令可能不会更新任何远程跟踪名称,但在此示例中,它是否更新都没有区别。

3使用filter-branch ,有一些refs/original/名称可以找到原始提交。 一旦你对一切看起来都感到满意,你必须在之后手动清理它们。 有关详细信息,请参阅文档

暂无
暂无

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

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