如何将使用 'git replace' 编辑的历史推送到远程
How to push history edited with 'git replace' to remote
我有一个遥控器,其历史记录如下:
如您所见,O 和 P 是合并提交,它们都关闭了它们的旧分支,所以现在只有一个分支。
我想将 C-D-E-G-J-K-L-N 压缩到一个提交中,将 F-H-I-M 压缩到另一个提交中,因为它们只是扰乱历史的微小提交。
在本地,我使用 the answer by John O'M. to this question 中描述的方法成功压缩了 C-D-E-G-J-K-L-N,如下所示:
git checkout -b squashing-1 N
git reset --soft C~
git commit -m "Squashed history"
git replace N [ID_of_the_commit_i_just_made]
这有效,来自主分支的本地 git log
正确报告 Q、P、O、X、M、I 等(X 是新压缩的提交)。
接下来的步骤是 (1) 检查主分支并合并更改,(2) 删除临时本地分支,然后 (3) 将更改推送到远程仓库。但是 (1) 和 (3) 报告 Already up to date 或 Everything is update 因为树没有实际变化 这正是所有这一切的重点。
我也尝试过使用 git push --force origin main-branch
和 git push --force-with-lease origin main-branch
,但我得到了相同的结果:一切都是最新的。
如何正确合并这些历史更改并将它们推送到 BitBucket 而无需重新创建整个存储库?
您基本上可以做出选择:您是想让每个人都使用替换引用,还是更愿意重写整个存储库并让每个人都有一个重要的标志日,在此期间他们从 "old repository" 到 "new repository"?这两种方法都不是特别有趣或有利可图。 :-)
替换工作原理
git replace
所做的是将一个新对象添加到Git 存储库中,并在refs/replace/
名称-space 中为其命名。这个名字中的名字-space就是新对象替换的对象的hash ID。例如,如果您要替换提交 1234567...
,新对象的名称(其 ID 不是 1234567...
——为了具体起见,假设它是 fedcba9...
)是 refs/replace/1234567...
.
Git的rest,查找对象时,先查看是否有refs/replace/<hash-id>
对象。如果是这样(并且未禁用替换),则 Git 的其余部分然后 returns refs/replace/
名称指向的对象,而不是实际对象。因此,当 Git 的某些其他部分读取了一些显示 "my parent commit is 1234567...
" 的提交时,Git 的其他部分会去寻找 1234567...
,发现 refs/replace/1234567...
存在,并且returns 对象 fedcba9...
代替。然后你会看到替换。
如果您 没有 引用 refs/replace/1234567...
,但是,您的 Git 永远不会交换替换对象。 (无论您是否 拥有 替换对象都是如此。是 引用本身 导致替换发生。拥有引用可以保证您有对象。)
因此,对于某些 other Git 要执行相同的替换过程,您 必须 交付 refs/replace/
引用另一个 Git.
将替换从一个 Git 转移到另一个
一般来说,你会推送这样的对象:
git push <repository> 'refs/replace/*:refs/replace/*'
(或具体列出您要推送的替换参考)。 获取这些对象:
git fetch <repository> 'refs/replace/*:refs/replace/*'
(您可以将此获取引用规范添加到每个克隆中的 fetch
配置。使用 git fetch
或 git fetch <repository>
将自动获取推送的任何新替换对象。推送仍在很痛苦,当然这个步骤必须在每个新克隆上重复。)
请注意,此处的 refspec 均未设置强制标志。如果发生这种情况,是否要强制覆盖现有的 refs/replace/
引用取决于您。
重写存储库
或者,一旦替换到位,您就可以 运行 存储库复制操作——我指的是逐个提交的复制,而不是像 git clone --mirror
这样的快速复制——比如 git filter-branch
。如果此复制操作是 运行 而没有禁用替换,则被替换的对象是 而不是 复制的;相反,它们的替换被复制。因此:
git filter-branch --tag-name-filter cat -- --all
在复制的存储库中具有永远 "cementing replacements" 的副作用。然后您可以丢弃所有原始引用 和 所有替换引用。 (执行此操作的简单方法是克隆过滤后的存储库。)
当然,由于这是一个新的不同的存储库,因此它不 与原始存储库或其任何克隆兼容。但它不再需要仔细协调 refs/replace/
名称-space(因为它不再有任何替换对象!)。
From here the next steps would be to (1) check out the main branch and merge in the changes, (2) delete the temporary local branch, then (3) push the changes to the remote repo.
看来您误解了 git replace
的真正作用。没有什么可以合并的,因为 git replace
没有以任何方式改变真实的历史。相反,replace
在 "by default, when browsing the history, if you find this object, substitute this one instead" 的旁边做了一个注释。您实际上仍然可以看到真实的历史,例如git --no-replace-objects log
.
因此 replace
创造了重写历史的 幻觉 。因为它不是真正的重写,因此不会为其他开发人员造成 "upstream rebase" 的情况,这非常酷。 OTOH 它不能作为一种从回购中清除敏感数据的方式来信任,因为重写实际上只是一种幻觉。您从 git 命令获得的输出可能会产生误导,因为它可能暗示 "real object" SHA ID 与 "replacement object" 内容相关联(事实上,基本上可以确定所说的内容不会散列到所说的 SHA)。
如果您决定继续并与 origin 共享替换,您真正需要做的是
git push origin refs/replace/*
请注意,有一些已知的 bugs/quirks,并且文档表明可能存在未知的 bugs/quirks。
注意:您需要确保服务器允许它:Git 2.19(2018 年第 3 季度)添加了一个新的配置变量 core.usereplacerefs
,主要是为了帮助服务器安装忽略
完全替换机制。
见commit da4398d, commit 6ebd1ca, commit 72470aa (18 Jul 2018) by Jeff King (peff
)。
(由 Junio C Hamano -- gitster
-- in commit 1689c22 合并,2018 年 8 月 15 日)
add core.usereplacerefs
config option
We can already disable replace refs using a command line option or environment variable, but those are awkward to apply universally. Let's add a config option to do the same thing.
That raises the question of why one might want to do so universally. The answer is that replace refs violate the immutability of objects. For instance, if you wanted to
cache the diff between commit XYZ and its parent, then in theory that never changes; the hash XYZ represents the total state.
But replace refs violate that; pushing up a new ref may create a completely new diff.
The obvious "if it hurts, don't do it" answer is not to create replace refs if you're doing this kind of caching.
But for a site hosting arbitrary repositories, they may want to allow users to share replace refs with each other, but not actually respect them on the site (because the caching is more important than the replace feature).
我有一个遥控器,其历史记录如下:
如您所见,O 和 P 是合并提交,它们都关闭了它们的旧分支,所以现在只有一个分支。
我想将 C-D-E-G-J-K-L-N 压缩到一个提交中,将 F-H-I-M 压缩到另一个提交中,因为它们只是扰乱历史的微小提交。
在本地,我使用 the answer by John O'M. to this question 中描述的方法成功压缩了 C-D-E-G-J-K-L-N,如下所示:
git checkout -b squashing-1 N
git reset --soft C~
git commit -m "Squashed history"
git replace N [ID_of_the_commit_i_just_made]
这有效,来自主分支的本地 git log
正确报告 Q、P、O、X、M、I 等(X 是新压缩的提交)。
接下来的步骤是 (1) 检查主分支并合并更改,(2) 删除临时本地分支,然后 (3) 将更改推送到远程仓库。但是 (1) 和 (3) 报告 Already up to date 或 Everything is update 因为树没有实际变化 这正是所有这一切的重点。
我也尝试过使用 git push --force origin main-branch
和 git push --force-with-lease origin main-branch
,但我得到了相同的结果:一切都是最新的。
如何正确合并这些历史更改并将它们推送到 BitBucket 而无需重新创建整个存储库?
您基本上可以做出选择:您是想让每个人都使用替换引用,还是更愿意重写整个存储库并让每个人都有一个重要的标志日,在此期间他们从 "old repository" 到 "new repository"?这两种方法都不是特别有趣或有利可图。 :-)
替换工作原理
git replace
所做的是将一个新对象添加到Git 存储库中,并在refs/replace/
名称-space 中为其命名。这个名字中的名字-space就是新对象替换的对象的hash ID。例如,如果您要替换提交 1234567...
,新对象的名称(其 ID 不是 1234567...
——为了具体起见,假设它是 fedcba9...
)是 refs/replace/1234567...
.
Git的rest,查找对象时,先查看是否有refs/replace/<hash-id>
对象。如果是这样(并且未禁用替换),则 Git 的其余部分然后 returns refs/replace/
名称指向的对象,而不是实际对象。因此,当 Git 的某些其他部分读取了一些显示 "my parent commit is 1234567...
" 的提交时,Git 的其他部分会去寻找 1234567...
,发现 refs/replace/1234567...
存在,并且returns 对象 fedcba9...
代替。然后你会看到替换。
如果您 没有 引用 refs/replace/1234567...
,但是,您的 Git 永远不会交换替换对象。 (无论您是否 拥有 替换对象都是如此。是 引用本身 导致替换发生。拥有引用可以保证您有对象。)
因此,对于某些 other Git 要执行相同的替换过程,您 必须 交付 refs/replace/
引用另一个 Git.
将替换从一个 Git 转移到另一个
一般来说,你会推送这样的对象:
git push <repository> 'refs/replace/*:refs/replace/*'
(或具体列出您要推送的替换参考)。 获取这些对象:
git fetch <repository> 'refs/replace/*:refs/replace/*'
(您可以将此获取引用规范添加到每个克隆中的 fetch
配置。使用 git fetch
或 git fetch <repository>
将自动获取推送的任何新替换对象。推送仍在很痛苦,当然这个步骤必须在每个新克隆上重复。)
请注意,此处的 refspec 均未设置强制标志。如果发生这种情况,是否要强制覆盖现有的 refs/replace/
引用取决于您。
重写存储库
或者,一旦替换到位,您就可以 运行 存储库复制操作——我指的是逐个提交的复制,而不是像 git clone --mirror
这样的快速复制——比如 git filter-branch
。如果此复制操作是 运行 而没有禁用替换,则被替换的对象是 而不是 复制的;相反,它们的替换被复制。因此:
git filter-branch --tag-name-filter cat -- --all
在复制的存储库中具有永远 "cementing replacements" 的副作用。然后您可以丢弃所有原始引用 和 所有替换引用。 (执行此操作的简单方法是克隆过滤后的存储库。)
当然,由于这是一个新的不同的存储库,因此它不 与原始存储库或其任何克隆兼容。但它不再需要仔细协调 refs/replace/
名称-space(因为它不再有任何替换对象!)。
From here the next steps would be to (1) check out the main branch and merge in the changes, (2) delete the temporary local branch, then (3) push the changes to the remote repo.
看来您误解了 git replace
的真正作用。没有什么可以合并的,因为 git replace
没有以任何方式改变真实的历史。相反,replace
在 "by default, when browsing the history, if you find this object, substitute this one instead" 的旁边做了一个注释。您实际上仍然可以看到真实的历史,例如git --no-replace-objects log
.
因此 replace
创造了重写历史的 幻觉 。因为它不是真正的重写,因此不会为其他开发人员造成 "upstream rebase" 的情况,这非常酷。 OTOH 它不能作为一种从回购中清除敏感数据的方式来信任,因为重写实际上只是一种幻觉。您从 git 命令获得的输出可能会产生误导,因为它可能暗示 "real object" SHA ID 与 "replacement object" 内容相关联(事实上,基本上可以确定所说的内容不会散列到所说的 SHA)。
如果您决定继续并与 origin 共享替换,您真正需要做的是
git push origin refs/replace/*
请注意,有一些已知的 bugs/quirks,并且文档表明可能存在未知的 bugs/quirks。
注意:您需要确保服务器允许它:Git 2.19(2018 年第 3 季度)添加了一个新的配置变量 core.usereplacerefs
,主要是为了帮助服务器安装忽略
完全替换机制。
见commit da4398d, commit 6ebd1ca, commit 72470aa (18 Jul 2018) by Jeff King (peff
)。
(由 Junio C Hamano -- gitster
-- in commit 1689c22 合并,2018 年 8 月 15 日)
add
core.usereplacerefs
config optionWe can already disable replace refs using a command line option or environment variable, but those are awkward to apply universally. Let's add a config option to do the same thing.
That raises the question of why one might want to do so universally. The answer is that replace refs violate the immutability of objects. For instance, if you wanted to cache the diff between commit XYZ and its parent, then in theory that never changes; the hash XYZ represents the total state.
But replace refs violate that; pushing up a new ref may create a completely new diff.The obvious "if it hurts, don't do it" answer is not to create replace refs if you're doing this kind of caching.
But for a site hosting arbitrary repositories, they may want to allow users to share replace refs with each other, but not actually respect them on the site (because the caching is more important than the replace feature).