Git 提交是快照,而不是差异。那么为什么需要 rebase 来删除旧的提交呢?

2023-12-24

我的理解是,无论出于何种意图和目的,Git 提交是快照,而不是差异 https://github.blog/2020-12-17-commits-are-snapshots-not-diffs/。因此,尽管 Git 会在内部“增量压缩”快照以消除冗余,但理论上每次提交都是代码库在任何一个时间点的完整表示,并且不需要先前的提交才有意义。 (我知道我在这一点上可能是错误的,但这是我目前的理解。)

因此,如果是这样的话,假设我有五个提交:

A - B - C - D - E

我决定不再关心 B 到 D - E 是我的规范承诺。

在这种情况下,我想我会使用git rebase -i HEAD~4然后要么drop or squash提交 B、C 和 D。

但如果提交是快照,为什么我需要这样做?在我看来,这意味着提交 E 取决于 B、C 和 D 中包含的历史记录,如果 Git 很天真,允许我“删除”B C 和 D,那么一切都会崩溃。这表明是差异系统,而不是快照系统。为什么 Git 不让我直接删除这些提交而不抱怨,而不是要求我将它们“rebase”到 A 上?难道一个完全基于快照的系统不允许我使用 E 作为“规范”提交而不关心它之前发生的事情吗?

如果我的错误理解能得到纠正,我将不胜感激。


你的问题存在的理由:你有

A - B - C - D - E

但你想要

A - E

美好的。让我们首先澄清您问题中的一些误解。

提交是不可变的

提交不仅仅是快照。他们也是不可变的。与 E 有任何不同的提交都不会是 E。它可能具有相同的内容提交消息为 E,但如果它与 E 完全不同,那么它就是不同的 commit.

亲子关系是提交的一部分

好吧,我刚刚说过了,但我会再说一遍。提交不仅仅是它们的内容。它们还包含许多其他内容,特别是有关其父母是谁的信息。

好的。现在,在您的图表中,D 是 E 的父级。没有 D 作为其父级的提交将具有不同的父级。我们刚刚说过,提交是不可变的。因此,如果我们想象 E 以 A 作为其父代,那不会是E。再说一次,我们可能call它以一种非正式的方式“E”;它可能具有与 E 相同的提交消息;但这将是一个不同的 commit.

无需剪切和粘贴

以上足以解释为什么你不能仅仅“剪切”B、C和D,然后“粘贴”E来追随A。你不能将E“粘贴”到任何地方!只有一个 E — 不仅在您的存储库中,而且在整个宇宙中 — 并且 D 是它的父级,这就是结束。

因此,为了从可见历史中消除 B、C 和 D,我们必须以某种方式rewriteE 制作一个不同的提交,以 A 作为其父级。这就是为什么需要重新设置基准(或类似的东西)才能生成您想要的历史记录的基本原因。历史

A - E

是不可能的。你能拥有的是

A - E'

其中第二个提交的名称为“e-prime”,在您的脑海中与 E 有一些相似之处,但它是notE. 你需要一种连贯的方式来做到这一点。这就是为什么你必须这样做some一种改变历史的舞蹈。

但这种舞蹈不需要重新定位——正如我们现在将看到的那样。喝一口咖啡,我们继续吧。

无需变基

现在假设你的问题的意思是这样的:B、C 和 D 是通往黄金真理的步骤,但 E 包含黄金真理,我真的不需要向我的人“展示我的工作”。同事和世界。因此,我想隐藏中间步骤,只是从 A 到 E(抱歉,到 E'),作为对所发生事件的纯粹而简单的陈述。

那么你实际上不需要变基。说啊

git reset --soft <SHA of A>
git commit -m 'message identical to the E message'

这将导致您想要的结果

A - E'

我们刚刚做了什么?我们从您自己陈述的事实开始:提交包含快照。当我们说reset --soft,我们基本上将该快照的内容生成到工作树和索引中。那么,当我们进行新的提交时,该提交就是项目的快照,其状态与 E 所描述的状态完全相同。但是当我们进行这个新提交时,HEAD 是 A。所以这个新提交的父级是 A!问题解决了。

所以是的,我们could已经重新设置并压缩了中间提交以获得相同的结果。但这对于那些不知道如何或不愿意打扰的人来说只是一种方便的方式来更直接地完成同样的事情。 Git 交互式 rebase 是某些常见类型历史转换的智能且方便的简写;但它没有做任何你自己以其他方式做不到的事情。

无需变基,ever

我只是想让你记住,你永远不需要变基。它所做的一切都可以通过一系列有时乏味且复杂的步骤来完成,这些步骤更加基本和直接,并且(可能)不方便。

例如,假设您想消除 B 和 C,但保留 D 和 E(实际上是 D' 和 E',正如我们所知 - D 将被 D' 替换,因为新提交的父级是 A,而不是 C,并且E 将被 E' 替换,因为新提交的父级是 D',而不是 D)。

您无需交互式变基即可完成此操作。在 D 处启动一个新分支。将该分支软重置回 A 并使用 D 的提交消息进行提交,就像我们之前所做的那样。现在,将 E 挑选到这个新分支的末尾,并给出结果提交 E 的旧消息。现在清理分支情况,就完成了。

我并不是说这比交互式变基更容易;而是说这比交互式变基更容易。显然不是。但这不是重点。关键是交互式 rebase 只是一个拐杖。一个非常棒的拐杖!但这不是魔法。

掉落不是南瓜

最后,你在问题中说了一些非常错误的话:你说“丢弃或挤压”。这里没有“或”:它们是完全不同的东西!压缩保留了一系列压缩中最后一次提交的内容。掉落则不然!如果您删除了 B、C 和 D,则生成的 E' 将包含看起来像的快照什么都没有就像现在的E!

例如,假设 B 包含一个新文件myfileA 没有。假设 C、D 和 E 也都有该文件。然后删除 B 将得到 E'lacks文件myfile.

这是因为,虽然提交不是差异,但差异do exist(在 Git 的心中,没有写入历史)并且他们are used在合并的过程中。变基实际上是一种合并形式(我现在不想讨论这个问题)。因此,通过删除 B,您将反转从 A 到 B 的差异。并且由于该差异的一部分是创建myfile,反转 diff 是一种说法not创造myfile。所以myfile当 rebase 结束时,不会出现在 E' 中,即使它did出现在E中。

压扁什么

最后但并非最不重要的一点是:您问题中的这句话也是错误的:“squash提交 B、C 和 D”。不。要通过交互式变基获得您想要的结果,您需要压缩 C、D 和 E。换句话说,选择列表最初看起来像这样:

pick f343cc4 B
pick f750aa9 C
pick 0105b79 D
pick 46fe327 E

你可以将其编辑为如下所示:

pick f343cc4 B
squash f750aa9 C
squash 0105b79 D
squash 46fe327 E

然后,您可以选择 E 的提交消息作为生成的新提交的提交消息。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Git 提交是快照,而不是差异。那么为什么需要 rebase 来删除旧的提交呢? 的相关文章

随机推荐