在 Mercurial 导入后修复 git
Repairing git after mercurial import
不久前,我将我的项目从 self-hosted mercurial 存储库移至 github。
现在看来,我对导入过程的结果检查不够仔细。我以一些未设置 parents 的提交结束。
例如,查看提交 fbf4e876f172e7a4a03153b801bd44cf71d98601. It has no parent and contains all files with all content, as if it would be the initial commit. But when I look into my original hg repository, this commit should have one parent - c1b215a3ae19dd9b6771a4ffe9217d6f9b65d4a9。
有什么方法可以重新定义无效提交吗?有时我需要使用 git 责备,但现在它给了我无用的结果。
首先,如果您没有对新的 Git 存储库进行任何操作并且仍然拥有原始的 Mercurial 存储库,那么最好的选择可能是使用任何命令重新转换 and/or 选项导致正确的转换。那就不用担心遗漏什么了。
如果失败,那么:
Is there any way to reparent the invalid commits?
有点,是的。但也没有。考虑使用 git replace
进行初始修复,然后 git filter-branch
重写存储库(之后所有用户必须切换到重写的版本)。
替换
从根本上说,问题是任何提交都不能更改。可以将一个提交 复制 到一个新的、稍微不同的提交,否则 "just as good as" 原始提交,这是解决问题的一种方法。
Git 有能力 "replace" 提交(或者实际上是任何 Git 对象),如果你愿意,可以使用 the git replace
command. The way this works under the covers is that the "replaced" commit remains in the repository intact, in its original form, and you make a new replacement object, usually by copying-but-changing-slightly the original object (although you can construct a new one from whole cloth。这个替换对象以 refs/replace/<hash-id>
形式的特殊外部名称进入存储库。每当 Git 通过哈希 ID 查找和使用原始对象时,它首先检查是否存在 refs/replace/<id>
引用。如果是这样,Git 将视线从原始对象转移到替换对象上。前端 git replace
命令大大减轻了这一过程的痛苦,但有一个重要警告:替换对象通常不会跨克隆传输。 这包括初始克隆和获取操作.
(他们可以,这并不难,但这不是默认设置。这是出于各种充分的理由,特别是包括安全性。无论从 PGP 签名中获得什么安全性如果允许替换,标签 and/or 提交会立即丢失。如果您可以直接访问存储库,那么您可以 运行 git replace
,您已经非常独立于加密安全;但是如果你克隆或获取,你依赖它。)
过滤分支
git filter-branch
命令完全是关于复制提交,并在此过程中应用一些更改(过滤器)。在复制一组以某个分支名称或其他引用指向的提交结尾的提交后,filter-branch
命令重写引用以指向新复制的链。
如果提交的副本与其原始副本完全相同,则副本实际上 是 原始副本。只有以某种方式发生变化的提交才会产生新的副本(具有不同的哈希 ID)。 filter-branch 代码仍然会复制每个提交,但有些副本最终会变成 "free" 这种方式。
考虑这个微小的、四次提交的存储库:
A--B--C--D <-- master
假设过滤器引入了对提交 C
的更改,使其变为提交 C'
,这意味着 Git 还必须将提交 D
复制到 D'
,D
和 D'
的区别在于 D'
链接回 C'
。过滤后的存储库包含:
A--B--C--D <-- refs/original/refs/heads/master
\
C'-D' <-- master
如果您根本不指定过滤器,Git 只会复制每个提交。如果您使用 --all --tag-name-filter cat
,Git 会将此过程应用于所有分支和所有标签。但是——这是关键——复制 使用替换。 所以,让我们绘制你自己的存储库,包括不正确的提交 fbf4e876f172e7a4a03153b801bd44cf71d98601
(我们称此为 F
简称)和具有正确父级 c1b215a3ae19dd9b6771a4ffe9217d6f9b65d4a9
的替换 F'
(我们简称为 C
):
...--C--... <-- refs/heads/somebranch
\
F' <-- refs/replace/F
F <--- tag: v2.0.0
(提交 F
没有父提交,所以它只是作为第二个根提交坐在那里)。
我们现在 运行 git replace --all --tag-name-filter cat
,所以 Git 发现所有引用都可以访问所有提交,包括 refs/heads/somebranch
和 refs/tags/v2.0.0
。所以它复制了 C
之前和之后的提交,但没有对它们进行任何更改。它去复制 F
但切换到替换 F'
,并复制 F'
也没有改变。
现在它已经复制了所有内容(F
除外),它使名称 refs/heads/somebranch
和 refs/tags/v2.0.0
指向副本。这对 somebranch
没有影响,但使 refs/tags/v2.0.0
指向替换提交 F'
.
过滤器分支代码添加 refs/original/
名称以保留原始分支提示和标记的提交,因此有一个 refs/original/refs/heads/somebranch
和 refs/original/refs/tags/v2.0.0
,您将丢弃它们。
通常,最简单的方法是克隆过滤后的存储库:此克隆不会获取 refs/replace/
引用,也不会获取 refs/original/
引用。它仅获取 refs/heads/
和 refs/tags/
引用。 (如果您不喜欢克隆方法,还有其他几种方法可以做到这一点。大多数方法会将提交的额外副本留在存储库中至少 30 天,直到它们自然过期并被垃圾收集。如果您只是替换了一些提交,这没什么大不了的。)
不久前,我将我的项目从 self-hosted mercurial 存储库移至 github。
现在看来,我对导入过程的结果检查不够仔细。我以一些未设置 parents 的提交结束。
例如,查看提交 fbf4e876f172e7a4a03153b801bd44cf71d98601. It has no parent and contains all files with all content, as if it would be the initial commit. But when I look into my original hg repository, this commit should have one parent - c1b215a3ae19dd9b6771a4ffe9217d6f9b65d4a9。
有什么方法可以重新定义无效提交吗?有时我需要使用 git 责备,但现在它给了我无用的结果。
首先,如果您没有对新的 Git 存储库进行任何操作并且仍然拥有原始的 Mercurial 存储库,那么最好的选择可能是使用任何命令重新转换 and/or 选项导致正确的转换。那就不用担心遗漏什么了。
如果失败,那么:
Is there any way to reparent the invalid commits?
有点,是的。但也没有。考虑使用 git replace
进行初始修复,然后 git filter-branch
重写存储库(之后所有用户必须切换到重写的版本)。
替换
从根本上说,问题是任何提交都不能更改。可以将一个提交 复制 到一个新的、稍微不同的提交,否则 "just as good as" 原始提交,这是解决问题的一种方法。
Git 有能力 "replace" 提交(或者实际上是任何 Git 对象),如果你愿意,可以使用 the git replace
command. The way this works under the covers is that the "replaced" commit remains in the repository intact, in its original form, and you make a new replacement object, usually by copying-but-changing-slightly the original object (although you can construct a new one from whole cloth。这个替换对象以 refs/replace/<hash-id>
形式的特殊外部名称进入存储库。每当 Git 通过哈希 ID 查找和使用原始对象时,它首先检查是否存在 refs/replace/<id>
引用。如果是这样,Git 将视线从原始对象转移到替换对象上。前端 git replace
命令大大减轻了这一过程的痛苦,但有一个重要警告:替换对象通常不会跨克隆传输。 这包括初始克隆和获取操作.
(他们可以,这并不难,但这不是默认设置。这是出于各种充分的理由,特别是包括安全性。无论从 PGP 签名中获得什么安全性如果允许替换,标签 and/or 提交会立即丢失。如果您可以直接访问存储库,那么您可以 运行 git replace
,您已经非常独立于加密安全;但是如果你克隆或获取,你依赖它。)
过滤分支
git filter-branch
命令完全是关于复制提交,并在此过程中应用一些更改(过滤器)。在复制一组以某个分支名称或其他引用指向的提交结尾的提交后,filter-branch
命令重写引用以指向新复制的链。
如果提交的副本与其原始副本完全相同,则副本实际上 是 原始副本。只有以某种方式发生变化的提交才会产生新的副本(具有不同的哈希 ID)。 filter-branch 代码仍然会复制每个提交,但有些副本最终会变成 "free" 这种方式。
考虑这个微小的、四次提交的存储库:
A--B--C--D <-- master
假设过滤器引入了对提交 C
的更改,使其变为提交 C'
,这意味着 Git 还必须将提交 D
复制到 D'
,D
和 D'
的区别在于 D'
链接回 C'
。过滤后的存储库包含:
A--B--C--D <-- refs/original/refs/heads/master
\
C'-D' <-- master
如果您根本不指定过滤器,Git 只会复制每个提交。如果您使用 --all --tag-name-filter cat
,Git 会将此过程应用于所有分支和所有标签。但是——这是关键——复制 使用替换。 所以,让我们绘制你自己的存储库,包括不正确的提交 fbf4e876f172e7a4a03153b801bd44cf71d98601
(我们称此为 F
简称)和具有正确父级 c1b215a3ae19dd9b6771a4ffe9217d6f9b65d4a9
的替换 F'
(我们简称为 C
):
...--C--... <-- refs/heads/somebranch
\
F' <-- refs/replace/F
F <--- tag: v2.0.0
(提交 F
没有父提交,所以它只是作为第二个根提交坐在那里)。
我们现在 运行 git replace --all --tag-name-filter cat
,所以 Git 发现所有引用都可以访问所有提交,包括 refs/heads/somebranch
和 refs/tags/v2.0.0
。所以它复制了 C
之前和之后的提交,但没有对它们进行任何更改。它去复制 F
但切换到替换 F'
,并复制 F'
也没有改变。
现在它已经复制了所有内容(F
除外),它使名称 refs/heads/somebranch
和 refs/tags/v2.0.0
指向副本。这对 somebranch
没有影响,但使 refs/tags/v2.0.0
指向替换提交 F'
.
过滤器分支代码添加 refs/original/
名称以保留原始分支提示和标记的提交,因此有一个 refs/original/refs/heads/somebranch
和 refs/original/refs/tags/v2.0.0
,您将丢弃它们。
通常,最简单的方法是克隆过滤后的存储库:此克隆不会获取 refs/replace/
引用,也不会获取 refs/original/
引用。它仅获取 refs/heads/
和 refs/tags/
引用。 (如果您不喜欢克隆方法,还有其他几种方法可以做到这一点。大多数方法会将提交的额外副本留在存储库中至少 30 天,直到它们自然过期并被垃圾收集。如果您只是替换了一些提交,这没什么大不了的。)