![](/img/trans.png)
[英]Can we export local commits from GIT repository and import them on another system
[英]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.
困难的部分是将没有关系的提交串在一起:如果他们的提交没有按编号引用您的提交,反之亦然,那么您的提交和他们的提交是不相关的。
我现在处于一种情况,我需要找到一种方法从源存储库“导入”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
(没有明显原因跳过了I
和J
)。
听起来您是在说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
将xyzzy
和somebranch
替换为找到提交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
,如果A
和P
完全匹配,所以我们根本不需要保留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
B'
P
O
现在看来,提交似乎从K
开始,向上运行并在H
结束。
他们没有——只有这个时髦的替代品——但 Git 的大部分都服从替代品。 git log
命令可以检测换出并包含一个标记,并且可以通过运行git --no-replace-objects command
告诉任何Git 命令忽略替换。 但大多数情况下,大多数 Git 命令服从替换。
请注意,如果A
和P
不匹配,源代码快照明智,您将需要使用git replace
将A
替换为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.