【问题标题】:How to push history edited with 'git replace' to remote如何将使用“git replace”编辑的历史推送到远程
【发布时间】:2017-10-17 03:08:42
【问题描述】:

我有一个遥控器,其历史记录如下:

你可以看到 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)报告已经是最新的一切都是最新的,因为树没有实际的变化这正是所有这些

我也尝试过使用git push --force origin main-branchgit push --force-with-lease origin main-branch,但我得到了相同的结果:一切都是最新的。


我怎样才能正确地合并这些历史更改并将它们推送到 BitBucket 而无需重新创建整个 repo?

【问题讨论】:

  • 这似乎需要做很多工作,但收效甚微。
  • 如果“大量工作”是指过于复杂,那么可以。

标签: git version-control merge bitbucket


【解决方案1】:

您基本上可以选择:您是希望每个人都使用替换引用,还是希望重写整个存储库并让每个人都有一个重要的标志日,在此期间他们从“旧存储库”切换到“新存储库”?这两种方法都不是特别有趣或有利可图。 :-)

替换的工作原理

git replace 所做的是将一个新对象添加到 Git 存储库中,并在 refs/replace/ 命名空间中为其命名。此命名空间中的名称是新对象替换的对象的哈希 ID。例如,如果您要替换提交 1234567...,则新对象的名称(其 ID 不是 1234567...——具体而言,假设它是 fedcba9...)是 refs/replace/1234567...

Git的rest,在查找对象的时候,首先检查是否有refs/replace/<hash-id>对象。如果是这样(并且替换未被禁用),Git 的其余部分将返回 refs/replace/ 名称指向的对象,而不是实际对象。因此,当 Git 的其他部分读取一些“我的父提交是 1234567...”的提交时,Git 的其他部分会去查找 1234567...,看到 refs/replace/1234567... 存在,并返回对象 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 refspec 添加到 fetch 配置中。使用 git fetchgit fetch &lt;repository&gt; 将自动拾取任何新推送的替换对象。推送仍然很痛苦,当然这一步必须在每个新克隆上重复。)

请注意,这里的 refspec 都没有设置强制标志。如果发生这种情况,您是否要强制覆盖现有的 refs/replace/ 引用取决于您。

重写存储库

或者,一旦你有了替换,你可以运行存储库复制操作——我的意思是逐个提交的复制,而不是像git clone --mirror这样的快速复制——比如git filter-branch。如果在没有禁用替换的情况下运行此复制操作,则被替换的对象被复制;相反,它们的替换被复制。因此:

git filter-branch --tag-name-filter cat -- --all

在复制的存储库中具有永远“巩固替换”的副作用。然后您可以丢弃所有原始引用所有替换引用。 (执行此操作的简单方法是克隆过滤后的存储库。)

当然,由于这是一个新的和不同的存储库,它与原始存储库或其任何克隆兼容。但它不再需要仔细协调 refs/replace/ 命名空间(因为它不再有任何替换对象!)。

【讨论】:

  • 我与replace 合作的次数不多(实际上必须在此处查找它才能提出答案);您知道使用替换参考时的具体缺点吗? (我问是因为“既不有趣也不有利可图”的评论......)
  • 替换对于 one 存储库非常有效;问题是它们默认不转移(在新克隆上),人们试图用它们来覆盖需要它们的东西。大国旗日对每个人来说都是一个巨大的痛苦,但它是一次性的痛苦。替换方法的痛苦较小,但仍在进行中。 :-)
  • 我希望我可以避免重写整个 repo,但由于这种方法是“水泥替换”,我认为这是迄今为止最好的选择。
【解决方案2】:

接下来的步骤是 (1) 签出主分支并合并更改,(2) 删除临时本地分支,然后 (3) 将更改推送到远程仓库。

您似乎误解了git replace 的真正作用。没有什么可以合并的,因为git replace 不会以任何方式改变真实的历史。相反,replace 在旁边写了一条注释,上面写着“默认情况下,在浏览历史记录时,如果您找到这个对象,请替换这个对象”。您实际上仍然可以看到真实的历史,例如git --no-replace-objects log.

所以replace 创造了一个重​​写历史的错觉。因为它不是真正的重写,因此不会为其他开发人员创建“上游变基”情况,这非常酷。 OTOH 不能相信它是一种从存储库中清除敏感数据的方法,因为重写实际上只是一种错觉。并且您从 git 命令获得的输出可能会产生误导,因为它可能暗示“真实对象”SHA ID 与“替换对象”内容相关联(事实上,基本上可以确定所述内容不会散列到所述 SHA )。

如果您决定继续并与 origin 共享替换,您真正需要做的是

git push origin refs/replace/*

请注意,有一些已知的错误/怪癖,并且文档表明可能存在未知的错误/怪癖。

【讨论】:

  • 我知道我会制造一种错觉,但你说得对,我对 git replace 所做的事情并不完全了解。这些步骤直接来自我在问题中链接到的答案。
【解决方案3】:

注意:您需要确保服务器允许它:Git 2.19(2018 年第三季度)添加了一个新的配置变量core.usereplacerefs,主要是为了帮助想要忽略的服务器安装 完全替换机制。

参见Jeff King (peff)commit da4398dcommit 6ebd1cacommit 72470aa(2018 年 7 月 18 日)。
(由 Junio C Hamano -- gitster -- 合并于 commit 1689c22,2018 年 8 月 15 日)

添加core.usereplacerefs 配置选项

我们已经可以使用命令行选项或环境变量来禁用替换引用,但是这些都很难普遍应用。让我们添加一个配置选项来做同样的事情。

这就提出了一个问题,为什么人们可能想要普遍这样做。答案是替换引用违反了对象的不变性。例如,如果你想 缓存提交 XYZ 与其父级之间的差异,然后理论上永远不会改变;散列 XYZ 表示总状态。
但是替换参考违反了这一点;推高一个新的 ref 可能会创建一个全新的差异。

如果你正在做这种缓存,那么明显的“如果它伤害,不要这样做”的答案是不要创建替换引用。
但是对于托管任意存储库的站点,他们可能希望允许用户彼此共享替换引用,但实际上并不在站点上尊重它们(因为缓存比替换功能更重要)。

【讨论】:

    猜你喜欢
    • 2015-08-29
    • 2017-04-11
    • 2019-01-16
    • 1970-01-01
    • 2017-07-18
    • 2013-11-25
    • 1970-01-01
    • 2019-05-08
    • 1970-01-01
    相关资源
    最近更新 更多