在复制 git 存储库后,如何取回共享历史记录?
How do I get shared history back after a git repository has been copied?
很久很久以前,在很远的办公室里,有人复制了一个 github 存储库并将其上传到 Visual Studio Team Services (VSTS)。我们开发人员愉快地编写代码,开发功能并修复 VSTS 中的错误。现在是时候将我们的代码重新发布到开源社区的怀抱中了...
不幸的是,我们的 VSTS 存储库与 github 存储库没有共享历史记录,因为它是副本,而不是克隆。虽然我们可以将 github 存储库添加为远程,但将我们的代码合并回主要分支是一个令人讨厌的冲突。整个文件夹结构已被移动或重命名,开源开发人员已提交对 github 存储库中的这些文件的更改。
有什么办法可以让我们的分支回到原来的地方吗?就像在复制存储库时将我们的整个分支树重新定位到 github 上的最后一次提交?
我想出的最好办法是将 VSTS 中的每个 CL 挑选到 github 上,这听起来像是一些严肃的侦探工作,以确定在何处插入重命名。
假设 VSTS 存储库是一个 Git 存储库,您可以:
- 克隆您的 GitHub 存储库
- 从正确的提交创建一个新分支
- 使用 VSTS 分支第一次提交的镜像副本覆盖工作树内容(以避免任何冲突解决)。然后添加并提交。
- git 从 VSTS 中挑选(添加为远程并获取)您的 VSTS 主分支的所有提交到新的本地分支(无冲突)
- 将新分支推回到 GitHub 仓库
这——将非克隆与实际克隆相结合——通常是困难的。
让我们写一个理论例子,以git://github.com/repo
为原型。假设 ssh://example.com/copy.git
将代表您使用以下命令序列设置的存储库:
<download tarball or zip file from github.com/repo>
<extract tarball or zip file into directory D>
$ cd D
$ git init
$ git add .
$ git commit -m initial -m "" -m "imported from github.com/repo.git"
之后,您从这个独立的存储库创建了位于 ssh://example.com/repo.git
的 --bare
存储库。
现在已经过了一段时间,您已经意识到您想要使用 github.com/repo.git
的实际克隆。唉,你的 ssh://example.com/repo.git
与 git://github.com/repo.git
没有共享的历史——没有共同的提交。 运行:
$ git clone ssh://example.com/repo.git combine
$ cd combine
$ git remote add public git://github.com/repo.git
$ git fetch public
获得所有 public 提交,但是尝试将 public/master
与您自己的私人 master
合并是一团糟。
在某些非常具体的情况下,修复此问题实际上并不难。诀窍在于将 root commit 现在位于 combine
存储库中,可从 master
访问,与 combine
存储库中的所有提交进行比较可从所有 public/*
个远程跟踪名称访问。如果你幸运的话,正好有一个提交的 tree
与你自己的根提交的 tree
完全匹配,因为你得到的 tarball-or-zip-file 生成了一个相同的树。
如果你不幸运的话,没有这样的提交。在这种情况下,您也许可以找到 "sufficiently close" 的提交。但是让我们假设您确实找到了一个提交,可从public/master
访问,它与您自己的根提交完全匹配:
A--B--...--o--o <-- master (HEAD), origin/master
\
... (there may be other branches)
C--...--R--...--o <-- public/master
在这里,大写字母 A
代表您自己的根提交的实际哈希 ID——您从下载的 tarball 或 zip 文件中创建的那个——而 B
只是提交在那之后。 C
代表可从 public/master
到达的(或某些)根提交,主要在图中仅用于说明:我们可以肯定的是,至少还有一个这样的根(无父)提交. 字母R
代表与您的提交A
完全匹配的提交,这是目前最有趣的提交。
我们现在想做的是假装 第二个-最有趣的提交的父项,B
, 是提交 R
而不是提交 A
。我们做得到! Git 有一个名为 git replace
的设施。 git replace
所做的是 复制 一个对象,同时进行一些更改。在我们的例子中,我们想要的是将提交 B
复制到一些新的提交 B'
中,它看起来几乎与 B
完全一样,但有一点改变了:它的父级。我们希望 B'
列出提交 R
.
的哈希 ID,而不是将提交 A
的哈希 ID 列为 B'
的父级
换句话说,我们将有:
A---------B--...--o--o <-- master (HEAD), origin/master
B'
/
C--...--R--...--o <-- public/master
现在我们要做的就是说服 Git 当它查找提交 B
时,它应该注意到有这个 替换 提交,B'
,并迅速将目光从 B
上移开,转而看向 B'
。这就是 git replace
所做的其余部分。因此,在找到提交 R
和 B
之后,我们 运行:
git replace --graft <hash-of-B> <hash-of-R>
现在 Git 假装 图形显示为:
B'-...--o--o <-- master (HEAD), origin/master
/
C--...--R--...--o <-- public/master
(嗯,Git 假装这个,除非我们 运行 git --no-replace-objects
看到现实)。
大的或小的缺点
除了定位提交 R
的相当艰巨的工作之外——查找 A
和 B
非常容易,它们是 git rev-list --topo-order master
列出的最后两个哈希 ID ——这个 git replace
把戏有一个缺陷。替换提交 B'
现在存在于我们的存储库中,但它通过特殊名称 refs/replace/<em>hash <em> 定位</em> </em>
,其中 hash
是原始提交 B
的哈希 ID。默认情况下,此替换对象(及其名称)不会发送到新克隆。
您可以制作 do 具有替换对象及其名称的克隆,并使用它们,一切正常。但这意味着每次有人克隆您的 combine
存储库时,他们必须 运行:
git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
或类似规则(此特定规则只是将您的克隆的 refs/replace/
命名空间从属于 origin
的命名空间,这是粗糙但有效的)。
或者,您可以声明一个 flag day and run git filter-branch
or similar to cement the replacement in place. I have described this elsewhere, though the best I can find at the moment is my answer to How can I attach an orphan branch to master "as-is"? 实际上,您创建一个 new 存储库,其中包含 B'
而不是 B
,但不有 A
,并且有 每个作为 B'
后代的提交的新副本(除了父哈希 ID 外,内容相同)。然后,您让所有用户从旧 repo.git
切换到新用户。这很痛苦,但只有一次。
如果您不打算长期使用组合存储库,这可能无关紧要。
除上述之外,您还可以使用嫁接历史来产生合并——Git命令通常会跟随替换——之后你可能不需要替换移植提交。在这种情况下,缺点是短暂的:它只会持续到您合并代码为止。
很久很久以前,在很远的办公室里,有人复制了一个 github 存储库并将其上传到 Visual Studio Team Services (VSTS)。我们开发人员愉快地编写代码,开发功能并修复 VSTS 中的错误。现在是时候将我们的代码重新发布到开源社区的怀抱中了...
不幸的是,我们的 VSTS 存储库与 github 存储库没有共享历史记录,因为它是副本,而不是克隆。虽然我们可以将 github 存储库添加为远程,但将我们的代码合并回主要分支是一个令人讨厌的冲突。整个文件夹结构已被移动或重命名,开源开发人员已提交对 github 存储库中的这些文件的更改。
有什么办法可以让我们的分支回到原来的地方吗?就像在复制存储库时将我们的整个分支树重新定位到 github 上的最后一次提交?
我想出的最好办法是将 VSTS 中的每个 CL 挑选到 github 上,这听起来像是一些严肃的侦探工作,以确定在何处插入重命名。
假设 VSTS 存储库是一个 Git 存储库,您可以:
- 克隆您的 GitHub 存储库
- 从正确的提交创建一个新分支
- 使用 VSTS 分支第一次提交的镜像副本覆盖工作树内容(以避免任何冲突解决)。然后添加并提交。
- git 从 VSTS 中挑选(添加为远程并获取)您的 VSTS 主分支的所有提交到新的本地分支(无冲突)
- 将新分支推回到 GitHub 仓库
这——将非克隆与实际克隆相结合——通常是困难的。
让我们写一个理论例子,以git://github.com/repo
为原型。假设 ssh://example.com/copy.git
将代表您使用以下命令序列设置的存储库:
<download tarball or zip file from github.com/repo>
<extract tarball or zip file into directory D>
$ cd D
$ git init
$ git add .
$ git commit -m initial -m "" -m "imported from github.com/repo.git"
之后,您从这个独立的存储库创建了位于 ssh://example.com/repo.git
的 --bare
存储库。
现在已经过了一段时间,您已经意识到您想要使用 github.com/repo.git
的实际克隆。唉,你的 ssh://example.com/repo.git
与 git://github.com/repo.git
没有共享的历史——没有共同的提交。 运行:
$ git clone ssh://example.com/repo.git combine
$ cd combine
$ git remote add public git://github.com/repo.git
$ git fetch public
获得所有 public 提交,但是尝试将 public/master
与您自己的私人 master
合并是一团糟。
在某些非常具体的情况下,修复此问题实际上并不难。诀窍在于将 root commit 现在位于 combine
存储库中,可从 master
访问,与 combine
存储库中的所有提交进行比较可从所有 public/*
个远程跟踪名称访问。如果你幸运的话,正好有一个提交的 tree
与你自己的根提交的 tree
完全匹配,因为你得到的 tarball-or-zip-file 生成了一个相同的树。
如果你不幸运的话,没有这样的提交。在这种情况下,您也许可以找到 "sufficiently close" 的提交。但是让我们假设您确实找到了一个提交,可从public/master
访问,它与您自己的根提交完全匹配:
A--B--...--o--o <-- master (HEAD), origin/master
\
... (there may be other branches)
C--...--R--...--o <-- public/master
在这里,大写字母 A
代表您自己的根提交的实际哈希 ID——您从下载的 tarball 或 zip 文件中创建的那个——而 B
只是提交在那之后。 C
代表可从 public/master
到达的(或某些)根提交,主要在图中仅用于说明:我们可以肯定的是,至少还有一个这样的根(无父)提交. 字母R
代表与您的提交A
完全匹配的提交,这是目前最有趣的提交。
我们现在想做的是假装 第二个-最有趣的提交的父项,B
, 是提交 R
而不是提交 A
。我们做得到! Git 有一个名为 git replace
的设施。 git replace
所做的是 复制 一个对象,同时进行一些更改。在我们的例子中,我们想要的是将提交 B
复制到一些新的提交 B'
中,它看起来几乎与 B
完全一样,但有一点改变了:它的父级。我们希望 B'
列出提交 R
.
A
的哈希 ID 列为 B'
的父级
换句话说,我们将有:
A---------B--...--o--o <-- master (HEAD), origin/master
B'
/
C--...--R--...--o <-- public/master
现在我们要做的就是说服 Git 当它查找提交 B
时,它应该注意到有这个 替换 提交,B'
,并迅速将目光从 B
上移开,转而看向 B'
。这就是 git replace
所做的其余部分。因此,在找到提交 R
和 B
之后,我们 运行:
git replace --graft <hash-of-B> <hash-of-R>
现在 Git 假装 图形显示为:
B'-...--o--o <-- master (HEAD), origin/master
/
C--...--R--...--o <-- public/master
(嗯,Git 假装这个,除非我们 运行 git --no-replace-objects
看到现实)。
大的或小的缺点
除了定位提交 R
的相当艰巨的工作之外——查找 A
和 B
非常容易,它们是 git rev-list --topo-order master
列出的最后两个哈希 ID ——这个 git replace
把戏有一个缺陷。替换提交 B'
现在存在于我们的存储库中,但它通过特殊名称 refs/replace/<em>hash <em> 定位</em> </em>
,其中 hash
是原始提交 B
的哈希 ID。默认情况下,此替换对象(及其名称)不会发送到新克隆。
您可以制作 do 具有替换对象及其名称的克隆,并使用它们,一切正常。但这意味着每次有人克隆您的 combine
存储库时,他们必须 运行:
git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*'
或类似规则(此特定规则只是将您的克隆的 refs/replace/
命名空间从属于 origin
的命名空间,这是粗糙但有效的)。
或者,您可以声明一个 flag day and run git filter-branch
or similar to cement the replacement in place. I have described this elsewhere, though the best I can find at the moment is my answer to How can I attach an orphan branch to master "as-is"? 实际上,您创建一个 new 存储库,其中包含 B'
而不是 B
,但不有 A
,并且有 每个作为 B'
后代的提交的新副本(除了父哈希 ID 外,内容相同)。然后,您让所有用户从旧 repo.git
切换到新用户。这很痛苦,但只有一次。
如果您不打算长期使用组合存储库,这可能无关紧要。
除上述之外,您还可以使用嫁接历史来产生合并——Git命令通常会跟随替换——之后你可能不需要替换移植提交。在这种情况下,缺点是短暂的:它只会持续到您合并代码为止。