git嫁接和替换有什么区别?(嫁接现在是否已被弃用?)

64

有关Git中的graftsreplace,几乎没有问答。搜索[git] +grafts +replace只找到了5个相关的问题中两个:what-are-git-info-grafts-forgit-what-is-a-graftcommit-or-a-graft-idgit.wiki.kernel.org:GraftPoint也有一条注记。

grafts现在是否已被replacefilter-branch完全取代,或者它们是否仍然需要用于某些特殊情况(以及向后兼容性)?

总的来说,它们有何不同(例如哪些在存储库之间传输),以及它们在基本上相同的方面如何运作?我看到Linus在有关提交生成编号的讨论中似乎目前并不关心grafts。“Grafts are already unreliable.”

编辑:发现更多信息。
在www.kernel.org/pub/software/scm/git/docs上搜索graft只找到了3个结果:

  1. git-filter-branch(1),
  2. v1.5.4.7/git-filter-branch(1),
  3. v1.5.0.7/git-svn(1)。

稍微广泛的搜索发现RelNotes/1.6.5.txt,其中包含:

  • refs/replace/层次结构旨在作为"移植"机制的替代方案,其优点是可以跨存储库传输。
  • 不幸的是,gitrepository-layout(5) 目前还没有更新有关refs/replace/存储库布局信息(和注释)的内容,也没有关于info/grafts信息的弃用说明。

    这更接近于支持我的想法,但我欢迎任何确认或澄清。


    是的,看起来嫁接已经成为不赞成使用的技术了,而replace可以完成嫁接所用的一切。 - Mariusz Pawelski
    3个回答

    20
    在你提到的关于提交生成编号相同讨论中,Jakub Narębski 确认嫁接更多的是问题而不是解决方案:

    嫁接是如此可怕的黑客,如果它们被使用,我不反对关闭生成编号。
    在替换对象的情况下,您需要非替换和替换DAG生成编号。
    [...] 嫁接是不可转移的,如果您将其用于削减而不是添加历史记录,则不安全防止垃圾回收... 我想。

    (出版始终由git filter-branch处理,如这个2008年有关嫁接工作流程的线程所示。)

    graftsgit replace之间的区别最好通过这个SO问题“将git父指针设置为不同的父级”以及(Jakub的回答)评论来说明。

    它包括对Git1.6.5的引用。

    据我所知(来自GraftPoints),git replace已经取代了git grafts(假设您使用的是1.6.5或更高版本的git)

    (Jakub:)

    • 如果您想要重写历史记录,那么grafts+ git-filter-branch (或交互式变基,或快速导出+例如reposurgeon)是完成此操作的方法。
    • 如果您想/需要保留历史记录,则git-replace远优于graft。

    12
    简化版翻译: “grafts” 可以定义一个已存在提交的替代祖先,而“replace” 定义了一个可替换现有提交(对象)的替代提交(对象),但您需要在创建该替换对象,而“graft” 不需要它的创建。但是,“replace” 引用会在仓库之间进行复制,而“grafts”则不会(我想)。 - Philip Oakley
    1
    @Philip:是的,我相信这概括了两者之间的区别。 - VonC
    Jacub的评论是针对“使用嫁接和替换对象修正历史DAG”而言的 - 我猜他指的是两者都需要。因此,在提交生成号码讨论中,它们都可能存在问题(除非找到一种使用传输的refs/replace的方法,但我怀疑它们[嵌入式生成号码]是否值得;将它们重新创建为本地索引/仓库的一部分,但不要传输它们。) - Philip Oakley
    1
    @VonC:现在我们需要的是一个graft2replace脚本,它将从grafts文件中获取一行并创建丢失的提交,将链接放入refs/replace中,然后我们就完成了;-) - Philip Oakley
    是的,我认为它只是作为用户在需要将本地修改合并到发布历史更新中时的本地辅助函数。也就是说,它必须在用户有这个目标的情况下由用户发起。 - Philip Oakley
    显示剩余2条评论

    14
    如果你需要使用 git replace 重写一个父提交记录,以下是操作步骤。 正如 Philip Oakley 提到的那样,git replace 只是用另一个提交记录替换一个提交记录。为了将一个父提交合并到现有的提交记录中,你需要先创建一个带有正确父节点的伪提交记录。 假设你有两个想要合并的git分支:
    (a)-(b)-(c) (d)-(e)-(f)
    

    现在我们希望(d)成为(c)的父级。因此,我们为(c)创建一个具有正确父级的替代品(让我们称其为c1),然后使用git replace将(c)替换为(c1)。在这些步骤中,每个字母都指代代表该提交的SHA1哈希。

    要创建新提交:

    git checkout d
    git rm -rf * # remove all files from working direcotry
    git checkout c -- . # commit everything from c over top of it
    git commit -a -C c # create replacement commit with original info
    

    现在您已经有了正确父节点为 (d) 的提交 (c1)。所以我们只需要用 (c1) 替换现有的 (c):

    git replace c c1
    
    现在您的历史记录如下所示:
    (a)-(b)-(c1)-(d)-(e)-(f)
    

    Bingo!


    不错的指南!唯一我错过的是日期格式,我不得不去谷歌搜索。显然,git接受RFC 2822(Thu, 07 Apr 2005 22:13:13 +0200)和ISO 8601(2005-04-07T22:13:13)格式的日期。 - Markus T
    2
    在最终的图中,d是c的父节点吗?当然,现在c(现在是c1)是d的父节点?应该是a-b-d-c1-e-f吧? - Will Dean
    git replace --graft <commit> <parent> 是最简单的方法。 - caot

    9
    EDIT: git replace --graft <commit> [<parent>…​] 命令与 grafts 相同,它可以添加或删除父级。 documentation 中说道:
    创建嫁接提交。创建一个新的提交,其内容与 <commit> 相同,但其父级将是 [<parent> …​] 而不是 <commit> 的父级。然后创建替换引用以用新创建的提交替换 <commit>。
    (我将旧答案保留作为参考。)
    据我所知,grafts可以处理但replace无法处理的一个用例:添加或删除父级。这是重构历史记录的强大工具。
    例如,如果你正在将旧的SVN存储库的历史记录导入Git中,那么没有合并信息。你可以做的(我已经做了很多次)是阅读提交消息,找出SVN“合并”发生的位置,然后使用Git grafts将父级添加到合并提交中。
    据我回忆,我也有一些情况下删除了提交的父级,以便将其作为历史记录中的第一个提交。基于多个混乱的遗留存储库创建干净的历史记录有时需要采取激烈措施(我的博客上有迁移项目到Git的一些经验)。
    然后,在清理整个历史记录之后,您需要在发布新的Git存储库之前执行git filter-branch

    1
    我认为 git replace [-f] <object> <replacement> 可以像 grafts 一样允许多个替换,但我(通过谷歌)没有找到任何证实这一点的内容。 - Philip Oakley
    1
    只需阅读代码(也可在 https://github.com/git/git/blob/master/builtin/replace.c 查看),它似乎只允许单个替换。但是该替换[提交]对象本身可能是一个合并,不过你必须先自己虚构它。 - Philip Oakley
    5
    关键更正是 git replace 可以执行所有接合操作的动作,包括从提交对象中添加或删除父节点。只需使用正确的父节点替换旧对象为新的提交对象即可。合并提交和单亲提交都属于相同类型的对象,因此可以通过 replace 自由互换。希望文档能够扩展以明确这一点。你能否修改你的回复? - Philip Oakley
    为什么 git replace --graft <commit> <parent>... 不够好? - Raedwald

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接