如何推送 "git replace --graft"

How to push a "git replace --graft"

我用 git replace --graft 记录了一个版本实际上是两个版本之间的(手动执行的)合并:

 git replace --graft <merged-version> <predecessor-version> <version-merged-from>

这对我的(本地、私有)存储库进行了更改。

我现在想通过 "pushing" 将更改提供给我们的共享存储库(在 Github 上),让我团队的其他成员也可以使用该更改。我怎么做?一个简单的git push好像没有效果。

移植物存在于 refs/replace/ 层次结构中。 (或者,最好说 "owe their existence to" 此类引用。)要将它们从一个存储库转移到另一个存储库,您必须推送或获取此类引用。

例如:

git push origin refs/replace/5c714d7798d1dc9c18d194fa6448680515c0ccdb

当提交 5c714d7798d1dc9c18d194fa6448680515c0ccdb 有替换时(在我的例子中,替换是新的提交对象 ceba978ce6dad3b52d12134f4ef2720c5f3a9002,即 Git 通常不会 "see" 5c714d7,寻找替换对象 ceba978)。

推送所有替换:

git push origin 'refs/replace/*:refs/replace/*'

(有时需要引号来防止 shell 破坏星号;确切的时间和使用哪种 种类 有点 shell-依赖,尽管单引号和双引号都适用于所有 Unix-y shells).

获取替换的注意事项

如果某些远程 R 有替换,并且您想将他们的所有替换到您的存储库中,请使用 git fetch <em>R</em> 'refs/replace/*:refs/replace/*'(或者如果你想让他们的替代品 override 与前缀 + 相同已经)。您可以为任何给定的存储库和远程自动执行此操作。例如,如果您 运行 git config --edit,您会发现您现有的 origin 遥控器有几个如下所示的设置:

[remote "origin"]
    url = ...
    fetch = +refs/heads/*:refs/remotes/origin/*

只需添加行:

    fetch = refs/replace/*:refs/replace/*

或:

    fetch = +refs/replace/*:refs/replace/*

让您的 Git 带来他们 Git 的 refs/replace/*。 (注意:这里不需要引号,因为 shell 不会处理这一行。)前导加号与通常的含义相同:1 没有它,如果你已经一些参考,你保留你的而忽略他们的。使用前导加号,您 丢弃您的并使用他们的 。与标签一样,如果您的参考和他们的参考已经匹配,那么您是保留您的还是用他们的替换都没有关系;这仅在您对某些引用应命名的对象有不同想法时才重要。


1其实前导加号的"usual meaning"取决于引用是否应该移动,比如分支名称,或 not 应该移动,例如标签名称。加号设置强制标志,即 "always take the proposed new setting",但对于分支名称(预期为 "move forward"),如果 且仅当 [=62= 时,允许不强制更新] 这是一个 "forward"(或 "fast forward")着法。 Git 最初也将此规则应用于其他引用,如标签,但 Git 人员在 Git 1.8.2 中修复了它。我不清楚哪些规则 Git 适用于 refs/replace/ 引用,这些引用不应移动,但不会像标签那样被特别对待。

使用 git replace --graft 时要小心:Git 2.22(2019 年第 2 季度)修复了一个错误,当给定一个指向 commit-ish 的标签时,“git replace --graft " 在编写替换引用之前未能剥离标签,这没有意义,因为该功能想要模仿的旧移植机制只允许用另一个替换一个提交对象。

commit ee521ec, commit f8e44a8, commit 5876170, commit 502d87b (31 Mar 2019) by Christian Couder (chriscool)
(由 Junio C Hamano -- gitster -- in commit ce2a18f 合并,2019 年 5 月 8 日)

replace: peel tag when passing a tag first to --graft

When passing a tag as the first argument to git replace --graft, it can be useful to accept it and use the underlying commit as a the commit that will be replaced.

This already works for lightweight tags, but unfortunately for annotated tags we have been using the hash of the tag object instead of the hash of the underlying commit.

Especially we would pass the hash of the tag object to replace_object_oid() where we would likely fail with an error like:

"error: Objects must be of the same type.
'annotated_replaced_object' points to a replaced object of type 'tag'
while 'replacement' points to a replacement object of type 'commit'."

This patch fixes that by using the hash of the underlying commit when an annotated tag is passed.

为了完整起见:git 替换是“虚拟的”,而不是永久的。被操纵的提交的原始版本仍然存在——它只是被替换提交遮蔽了。 描述了如何将这些“虚拟替换”也发布到共享存储库中,以及如何在获取时安排获取此类替换。通常这是正确的做法。

但是,有时我们希望永久修复此类历史记录。使用Git,唯一的方法是合成一个新的历史。这可以用git filter-branch(脆弱的,低级的)或非常Gitub 上的好工具 git-filter-repo(Git 项目官方推荐)。

但是请注意,无法强制共享存储库的其他用户使用重写的历史记录。您需要让他们切换,例如通过重置他们的主分支或切换到另一个新分支。因此,在 public 设置中,永久重写历史是不可行的;但是对于一个封闭的用户组,例如在商业设置中,这是一个非常有效的选项(并且可能确实有必要删除一些敏感内容,如凭据)