合并请求挤压提交选项安全吗

Pau*_*l W 5 git gitlab

我的开发团队最近开始使用 Gitlab。我曾建议,在合并请求中我们需要压缩提交。我遭到很多反对,认为这不安全。

典型的功能开发周期包括功能分支上的每日提交和推送。它还包括每天一次 git 从 master 拉取,以与其他更改保持同步。有时这涉及解决合并冲突。

他们认为所有这些从 master 合并,然后向 master 执行合并请求并压缩提交可能会导致可能未检测到的合并问题。

有没有道理呢?我的假设是,无论如何,压缩提交都应该是安全的。

tor*_*rek 13

冒着重复bk2204 的答案(部分甚至全部)内容的风险,我将对此进行尝试。

\n\n

(请注意,任何答案都存在问题,因为您使用了“安全”这个词。这不是一个具有清晰明显的技术定义的术语。在一种情况下完全安全的东西在另一种情况下可能不太安全。类比总是如果用力过猛,就会崩溃,但请考虑一下,例如,在没有穿戴手部、膝盖和肘部保护的情况下走在街上,与穿着溜冰鞋\xe2\x80\x94 在街道上疾驰\xe2\x80\x94 或者我应该说“直排轮滑鞋”吗?\xe2 \x80\x94 没有上述保护。)

\n\n

我总是喜欢说 Git 与文件无关,而是与提交(然后保存文件)有关。这在本例中也成立:

\n\n
    \n
  • 每次提交都有(所有)文件的快照以及一些元数据。
  • \n
  • 合并只是根据一组定义的规则组合快照\xe2\x80\x94(通常是其中 3 个\xe2\x80\x94)。
  • \n
  • 常规合并和挤压合并使用相同的机制来实现要进入下一次提交的最终文件集。
  • \n
  • 但是, (或其在某些 Web 界面上的等效点击按钮)所做的提交与(或其等效点击按钮)git merge --squash所做的提交不同。git merge
  • \n
\n\n

在这种情况下,全部差异在于最终提交的父链接。从表面上看,这似乎是一个微小的差异……但是 Git 中的提交链接方式已经成为历史。没有文件历史记录这样的东西:只有提交历史记录。因此,这个微小的差异实际上是巨大的\xe2\x80\x94,特别是当你考虑挤压合并通常会如何影响人们的未来行为时。

\n\n

要了解其在实践中的工作原理,有必要了解提交图。这让我们了解了我在上面第一个要点中提到的元数据。

\n\n

可视化提交图

\n\n

我发现有时查看真实提交的实际具体内容很有帮助。这是 Git 本身在 Git 存储库中的提交:

\n\n
$ git rev-parse HEAD\n08da6496b61341ec45eac36afcc8f94242763468\n$ git cat-file -p 08da6496b61341ec45eac36afcc8f94242763468 | sed \'s/@/ /\'\ntree 27fee9c50528ef1b0960c2c342ff139e36ce2076\nparent 07f25ad8b235c2682a36ee76d59734ec3f08c413\nauthor Junio C Hamano <gitster pobox.com> 1570770961 +0900\ncommitter Junio C Hamano <gitster pobox.com> 1570771489 +0900\n\nEighth batch\n\nSigned-off-by: Junio C Hamano <gitster pobox.com>\n
Run Code Online (Sandbox Code Playgroud)\n\n

(我用@空格替换了 可能只是为了减少发送到 pobox.com 的垃圾邮件数量。请注意,您实际上也可以git cat-file -p HEAD:我在这里使用原始哈希 ID 来帮助巩固这样的想法:是重要的哈希 ID。)

\n\n

这实际上是提交的完整文本。哈希 ID08da6496b61341ec45eac36afcc8f94242763468是该内容的加密校验和。1 Git 间接将快照本身存储为对象,2它有自己的哈希 ID。文本的其余部分是元数据的其余部分:作者和提交者(姓名、电子邮件地址以及日期和时间戳);父提交,按哈希 ID 列出;和日志消息。

\n\n

因此,每个提交都有自己唯一的哈希 ID。该哈希 ID 指的是提交;其他提交不能具有相同的哈希 ID。3 此外,每个提交通过哈希 ID 引用其直接提交。这些父哈希 ID 必须是在提交之前存在的某些提交的 ID。对于普通(非合并)提交,(单个)父级是您在提交时签出的任何提交。

\n\n

当我们查看一个只有 3 个提交的新 Git 存储库时,我们可以通过使用单个大写字母代表实际的哈希 ID 来得出这一点:

\n\n
A <-B <-C\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,commitA是我们所做的第一个提交。它没有父项,因为父项是在 commit 之前存在的提交A,并且没有父项。这种特殊情况就是 Git 所说的根提交4 但是一旦我们提交了A,我们就进行了提交B,所以B\ 的父级是AB类似地,我们在进行 commit 时使用了 commit C,因此C\ 的父级是B

\n\n

当某个东西保存了提交的哈希 ID 时,我们说该东西指向该提交。所以 commitC指向B,它又指向A,这是一个根提交,但那里没有任何东西指向。

\n\n

在这些图中,很容易看出链的终点在哪里,但在真实的 Git 存储库中,具有真实的\xe2\x80\x94 和随机的\xe2\x80\x94 哈希 ID,这很难。此时,我们需要一种快速简便的方法来找到commit 。因此,Git 为我们提供了分支名称C,部分是为了安慰我们这些无法记住原始哈希 ID 的人。

\n\n

分支名称仅保存一次提交的哈希 ID。在我们的例子中,我们将使用该名称master来保存我们正在调用的提交的实际哈希 IDC。我们可以这样画:

\n\n
A--B--C   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

该名称master指向链中的最后一次C提交,即. 我已经停止绘制内部箭头,因为它们很快就会变得更难绘制。请记住,它们总是指向后面。它们是parent实际提交中的行的结果,因此它们永远不能更改 xe2x80x94 ,这与分支名称箭头不同改变。

\n\n

让我们通过执行然后更新文件、-ing 和-ingD向我们的链添加一个新的提交。新提交将指向现有提交作为\ 的父级:git checkout mastergit addgit commitDCD

\n\n
A--B--C   <-- master\n       \\\n        D\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后,由于D 这是链的最后一次提交,Git 将更新我们当前的分支名称 ,master以指向D而不是C。我们还可以D在图中向上移动一条线。结果是:

\n\n
A--B--C--D   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

要添加分支,我们只需创建一个新名称,指向当前提交。让我们创建新分支dev并让它指向D

\n\n
A--B--C--D   <-- dev, master\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们需要一点额外的符号:我们必须记住我们签出了哪个分支名称。我们已经提交D了,但是当我们添加新的提交时E,只有两个分支名称之一应该移动为此,我们将(HEAD)在其中一个分支名称后添加。这就是我们的名字git commit将移动该名称。因此git checkout dev,如果我们继续提交D\xe2\x80\x94 及其所有文件 \xe2\x80\x94,然后当我们进行新的提交时,我们会得到:

\n\n
A--B--C--D   <-- master\n          \\\n           E   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

1从技术上讲,它是单词的 SHA-1 校验和,commit后跟一个空格,后跟以十进制表示的字节大小,即280,后跟 ASCII NUL 字符,后跟内容的字节:

\n\n
$ (printf \'commit 280\\0\'; git cat-file -p HEAD) | shasum\n08da6496b61341ec45eac36afcc8f94242763468  -\n
Run Code Online (Sandbox Code Playgroud)\n\n

该技术适用于 Git 的所有四种对象类型:打印类型名称、空格、十进制 ASCII 格式的对象大小(以字节为单位)、ASCII NUL,然后打印内容。对于提交,内容是提交的文本,顺序为:tree和散列,每个父级按顺序,author行,committer行,任何其他标题行,空行,主题和正文。对于带注释的标签,请查看任何带注释的标签。Blob 对象具有明显的文本。棘手的是树对象,它是二元的,并且有几个半任意规则应用于强制排序。树对象将成为转换为 SHA-256 的最大障碍。

\n\n

2这允许共享树对象:如果两个不同的提交保存相同的快照,则它们不需要重新保存树。这种共享递归地应用于子树,当然也应用于单个 blob 对象。毕竟,一旦写入一个对象,它的任何部分都无法更改\xe2\x80\x94,哈希 ID 校验和将会改变,结果将是一个不同的对象\xe2\x80\x94,所以如果你有相同的内容,它会减少到相同的哈希 ID。对象数据库是一个简单的键值存储,它检测到键已经存在,因此不会再次存储数据。

\n\n

3请注意,如果您使用相同的树、相同的作者、相同的日志消息和\xe2\x80\x94(至关重要的是\xe2\x80\x94)进行相同的提交,并且具有相同的时间戳和parents,那么您将获得相同的提交。如果其中任何一个不同,包括父哈希或时间戳,您将获得一个新的不同提交。因此,如果您重新提交相同的快照,则会在不同的时间并使用不同的父提交来执行此操作:因此,新提交与旧提交不同。因此,新的提交会获得新的哈希值。即使您使用GIT_AUTHOR_DATEGIT_COMMITTER_DATE强制新提交共享旧提交的时间戳,父级哈希也会不同:新提交引用回某个其他现有提交,与原始提交的父级不同( s),同样,重新提交相同的快照会给您一个新的、不同的提交。

\n\n

让 Git 重新使用提交哈希的唯一方法是使用相同的父项同时提交相同的快照。在这种情况下,可以重新使用原始提交,因为新提交就是原始提交。它们确实是相同的。没有人能区分它们,所以它们可能是同一个提交。

\n\n

4您可以生成具有多个根提交的提交图,但许多存储库只有一个原始根。需要使用几个稍微奇怪的技巧之一来获取额外的根提交。

\n\n
\n\n

合并提交和提交图

\n\n

在 Git 中,合并提交是指至少有两个父项(通常只有两个父项)的任何提交。在图中出现分叉之前,我们无法进行合并提交。例如,假设该图在提交之前是线性的H,之后它会像这样发散:

\n\n
          I--J   <-- master (HEAD)\n         /\n...--G--H\n         \\\n          K--L   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们已经完成了git checkout master,这意味着我们J现在已经提交了签出。该名称dev标识 commit L

\n\n

我们现在跑git merge dev。Git 使用该来查找两个分支最后聚集在一起的点,在本例中显然是 commit H。Git 将其称为合并基础提交。另外两个有趣的提交是我们当前的提交,J以及名称dev指向的提交,L提交。

\n\n

Git 现在实际上将:

\n\n
    \n
  • H比较合并基础提交与当前提交的内容J:这是我们自基础提交以来更改的内容;
  • \n
  • diffH与其他提交L:这是他们自基础以来所做的更改;
  • \n
  • 尽最大努力将这两组变化结合起来。
  • \n
\n\n

组合的更改将应用​​于 commit 的内容H。这样,我们就可以得到我们的更改以及他们的更改。组合过程的精确细节可能会变得非常棘手,尤其是在面临冲突时,但在这里,我们对结果更感兴趣。我们假设没有冲突。

\n\n

由于我们运行了git merge(不是git merge --squash),Git 现在将提交结果。新的提交推进了我们当前的分支,就像任何其他提交一样,但这次新的提交指向两个分支提示提交。因此我们可以画出这样的结果:

\n\n
          I--J\n         /    \\\n...--G--H      M   <-- master (HEAD)\n         \\    /\n          K--L   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

CommitM两个父母,J并且L.

\n\n

当然,如果我们愿意,我们可以添加更多的日常提交:

\n\n
          I--J\n         /    \\\n...--G--H      M--N   <-- master (HEAD)\n         \\    /\n          K--L   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

像往常一样,新提交NM其父提交。当我们让 Git 向我们显示可从 访问的 提交时master,Git 将从 开始N,然后后退一步M并向我们显示提交M。现在 Git 有一个选择:是向后退一步到J,还是退一步到L

\n\n

一般来说,Git 实际上同时完成了这两件事。5 父级中的任何一个都不比另一个“更好”或“更重要”,因此 Git 将跟踪 \xe2\x80\x94 合并的两条腿\xe2\x80\x94 并访问提交JI,但也L访问 和K。这些轨迹在 重新组合H,因此一旦 Git 到达,它就可以像以前一样H移动到G等。F

\n\n

请注意,合并提交M有自己的快照,就像任何其他提交一样。这里唯一特别的M是它有两个父母。历史,从M向后推演,沿着两条腿走下去,然后重新汇合。

\n\n

这如此重要的原因之一是未来合并的合并基础取决于当前合并。让我们画一个稍微不同的变体,其中dev被合并master不止一次。我们将从一次合并M开始master如下所示:

\n\n
...--H---L--M--P   <-- master\n      \\    /\n       I--J--N--O   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

(是的,没有K,我把它遗漏了以使合并得到字母M。:-))

\n\n

现在我们将git checkout mastergit merge dev进行新的合并q。Git 必须找到 commitP和 commit之间的合并基础O。为此,它从 向后工作P,转到M,然后再转到LJ。它也可以向后运行 from O、going toN和 then J

\n\n

提交J可以两个分支访问,并且是正确的合并基础。它合并基础,因为 M链接回L J。因此,从M到 的反向链接J在这里很重要。如果我们没有它,Git 就必须继续前进,从LH,从JIH

\n\n

换句话说,如果没有 merge commit 提供的链接M下一次合并将不得不查看 to (我们的更改)和 from to (他们的更改)的差异HP其中H包括O合并的更改通过链接,Git 会查看 from Jto P\xe2\x80\x94 的差异,这实际上是我们在Land P\xe2\x80\x94 中所做的事情,以及 from Jto O,这实际上是他们在N和 中所做的事情O。因此后续的合并更容易(当然,这不是微不足道的,但更容易)。

\n\n
\n\n

5对于git log其他一些 Git 命令,您可以用来--first-parent告诉它忽略第二个父级以及任何其他父级(如果存在),以实现该命令的目的。请注意,合并的第一个父级是在进行合并时选择的。以后无法更改。它始终是您或任何人进行合并时正在进行的提交。由观看者决定第一个父母是否比任何其他父母“更重要”,但这里唯一的内置标志是--first-parent:没有--second-parent,所以要么所有父母都是平等的,要么第一个- 父母更重要。

\n\n
\n\n

挤压合并不是合并

\n\n

假设我们不是用 进行合并Mgit merge dev而是用git merge --squash dev。Git 仍然会:

\n\n
    \n
  • 找到合并基数H
  • \n
  • 差异HL看看我们改变了什么;
  • \n
  • diffHJ看看它们改变了什么;和
  • \n
  • 组合两个差异,将组合应用于来自 的快照H
  • \n
\n\n

命令行git merge --squash标志也设置该--no-commit标志,以便 Git 强制我们运行git commit以进行新的提交M。Web 界面上的大多数点击按钮都会继续并自行提交,并且命令行 Git 没有充分的理由像这样停止,除非向后兼容性需要它。无论如何,当我们进行合并\xe2\x80\x94或一些点击按钮\xe2\x80\x94时,我们得到:

\n\n
...--H---L--M   <-- master\n      \\\n       I--J   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

其他一切都完全相同,但 commitM没有指向dev.

\n\n

如果我们现在像以前一样继续并有:

\n\n
...--H---L--M--P   <-- master\n      \\\n       I--J--N--O   <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们运行的下一次合并,无论有没有,--squash都会将提交与提交和进行比较。如果我们在创建时必须对合并冲突采取一些巧妙的措施,那么我们可能不得不再次这样做。devmasterHPOM

\n\n

这在实践中意味着,在 后git merge --squash,您几乎总是应该删除刚刚合并的分支。也就是说,合并 commit 后J,您应该删除name dev

\n\n
...--H---L--M   <-- master\n      \\\n       I--J   [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n\n

提交IJ在您自己的存储库中保留一段时间\xe2\x80\x94具体要多长时间有点复杂\xe2\x80\x94但是没有名称来查找它们,您必须记住它们的哈希ID,或者至少是提交的哈希 ID J

\n\n

并发症

\n\n

如果有人将其他名称指向提交I或或我们可以从中找到J的任何提交,那么这里的情况会更加复杂。例如,如果我们有: JI

\n\n
...--H---L--M   <-- master\n      \\\n       I--J   <-- dev\n        \\\n         K   <-- d2\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我们删除 name dev,这样就J无法找到提交,除非你记住它的哈希 ID,我们最终会得到如下所示的结果:

\n\n
...--H---L--M   <-- master\n      \\\n       I--K   <-- d2\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在看来,提交I本身是我们在工作时所做的重要事情,而不是在开始工作并进行提交之后d2开始工作的副作用。d2devI

\n\n

这是关于 Git 的一般规则,而不是特定于压缩合并的规则:重要的是提交而不是分支名称。分支名称只是我们用来查找提交的起始\xe2\x80\x94或结束\xe2\x80\x94点。因此,当您移动或删除分支名称时,如果仍然可以从其他分支名称找到提交,那么它们仍然在那里。即使找不到它们它们也可能仍在您的存储库中的某个位置。在确定它们是不需要的垃圾之后,您的 Git 最终会将它们扔掉。

\n\n

我们通过从分支名称开始并向后工作来查找提交。当我们以这种方式发现某些提交时,我们说它在分支上,但更准确的说法可能是它包含在分支内。任何一项提交都可能包含在许多分支中。当我们添加、删除或移动分支名称时,包含提交的分支集会发生更改。

\n\n

所以我们必须记住的规则是:除非添加提交或无法找到提交,否则提交永远不会改变。大多数时候,分支名称一直在变化\xe2\x80\x94,这样我们就可以找到新的提交不断找到旧的提交。

\n\n

考虑到所有这些,让我们看看您的具体情况

\n\n

我将跳过添加多个 Git 存储库时出现的大部分复杂情况。Agit push只是将提交发送到其他一些 Git 存储库,要求它们将其中一个分支名称设置指向这些新提交中的最后一个。Agit pull首先运行git fetch,它从其他 Git 存储库获取任何新的提交,然后更新您自己的 Git\ 的远程跟踪名称 \xe2\x80\x94origin/master等 \xe2\x80\x94 以记住对应的最后一次提交分支。有时,您可能会在进行真正的合并(如上所述)之后执行新的合并提交。有时它会执行快进操作,这相当于检查他们的提示提交并将您自己的分支名称向前拖动以包含由此引入的所有新提交。当然,只是意味着运行,然后运行第二个 Git 命令,通常是git mergegit fetchgit pullgit fetchgit merge

\n\n

复杂性很重要,但实际的合并(如果有的话)总是发生在我们自己的存储库中。6 我们还git fetch根据需要其他人的提交。我们最终在本地保存所有提交,并且可以可视化 Git\xe2\x80\x94 的提交,即使它是从其他 Git\xe2\x80\x94 获取的,就像我们的存储库中存在的那样。

\n\n
\n

典型的功能开发周期包括功能分支上的每日提交和推送。它还包括每天一次 git 从 master 拉取,以与其他更改保持同步。有时这涉及解决合并冲突。

\n
\n\n

经过几天的工作,我们最终得到了一个如下所示的图表:

\n\n
...--H--K--L--N--O--R   <-- master\n      \\     \\     \\\n       I--J--M--P--Q--S   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

其中MQ是真正的合并提交,其中一些可能涉及解决合并冲突。

\n\n

如果我们相信现在feature已经准备好了,我们可以:

\n\n
git checkout master\ngit merge feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将产生:

\n\n
...--H--K--L--N--O--R---T   <-- master\n      \\     \\     \\    /\n       I--J--M--P--Q--S   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

的两个父母TR(第一父母)和S(第二父母)。我们现在可以feature完全删除该名称:

\n\n
...--H--K--L--N--O--R---T   <-- master\n      \\     \\     \\    /\n       I--J--M--P--Q--S\n
Run Code Online (Sandbox Code Playgroud)\n\n

S通过返回到其第二个父级,提交仍然是可访问的T。合并本身是通过将提交中的合并基础快照与提交中的快照进行比较并合并O它们来完成的。RS

\n\n

或者,我们可以git merge --squash,之后我们真的应该feature完全删除。删除前的结果feature如下:

\n\n
...--H--K--L--N--O--R--T   <-- master\n      \\     \\     \\\n       I--J--M--P--Q--S   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这两种情况下, commit 的内容T都是相同的;只是T这次没有第二个父级(所以根据定义它不是合并提交)。

\n\n

假设我们不尝试通过哈希 ID 来记住提交,而只是忘记所有无法访问的提交,那么删除名称时的结果feature如下所示:

\n\n
...--H--K--L--N--O--R--T   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

6通过点击 Web UI 按钮,合并发生在他们的Git 存储库中,之后我们必须git fetch合并它们,然后更新我们自己的分支名称以合并新的合并提交。一般来说,托管服务不允许存在冲突的合并,因为托管服务不提供解决冲突的方法。随着时间的推移,一些托管服务可能会添加此类功能,但我更愿意自己在本地进行冲突合并:通过这种方式,您可以轻松获得更多数据。

\n\n
\n\n

结论

\n\n

您的问题询问了安全性,但如果没有安全的定义,就无法真正回答。I我们可以说的是,我们在单独提交、JP、中所做的工作S不再可找到。相反,当我们查看仍然可以找到的快照时,看起来好像有人feature在一夜之间编写了所有代码/更改。

\n\n

这有好的方面,也有坏的方面。对于某些功能,好处多于坏处:没有分散注意力的合并提交的第二父级T来查看单独的提交,例如IJPS,并且无需担心该路径中的任何合并提交。但是,如果一体式更改中出现错误怎么办T也许这个 bug 在 commitJ或 commit中很容易找到P,而在T. 因此,也许保留个人承诺会更好。

\n\n

如果您确定您永远不想恢复原始提交,则压缩是可行的方法。如果您不确定,则可能不是。

\n\n

有一种妥协的立场,从某些方面来说,这可能是最好的一种。您可以将分支重新设置dev,而不是挤压合并feature。rebase 的作用是复制提交。不过,变基也有其自身的缺点。

\n\n

这是合并之前的内容:

\n\n
...--H--K--L--N--O--R   <-- master\n      \\     \\     \\\n       I--J--M--P--Q--S   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

有 6 个提交可通过名称访问feature,但无法通过名称访问masterIJMPQS

\n\n

这样做git checkout feature; git rebase master将指示 Git 找到这六个提交,从列表\xe2\x80\x94中丢弃任何合并提交,留下I-J-P-S\xe2\x80\x94 ,然后一次一个复制R这些提交,以便新副本出现在 commit 之后。每个副本都是由 完成的git cherry-pick7 不幸的是,每个选择都可能存在冲突,如果是这样,您必须解决它们。它们往往与您在现在省略的合并中可能已解决的冲突相同,并且往往需要相同的解决方案。8 复制完成后,Git 移动分支名称,最终结果如下所示:

\n\n
                      I\'-J\'-P\'-S\'  <-- feature\n                     /\n...--H--K--L--N--O--R   <-- master\n      \\     \\     \\\n       I--J--M--P--Q--S   [abandoned, but the name ORIG_HEAD works to find S]\n
Run Code Online (Sandbox Code Playgroud)\n\n

名称I\'J\'等意味着即使新提交具有新的且不同的哈希 ID,它也是原始提交的副本。

\n\n

您可以将副本中的快照S\'与使用中的快照进行比较S

\n\n
git diff ORIG_HEAD HEAD\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果它们不匹配,您可能在重新解决冲突时犯了错误。9

\n\n

现在您已经将所有内容整齐地重新设置了基础,您可以执行快进合并:

\n\n
                      I\'-J\'-P\'-S\'  <-- feature, master\n                     /\n...--H--K--L--N--O--R\n
Run Code Online (Sandbox Code Playgroud)\n\n

或真正的合并:

\n\n
                      I\'-J\'-P\'-S\'  <-- feature\n                     /          \\\n...--H--K--L--N--O--R------------T   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后在任何一种情况下,feature只要应该删除该名称,就将其删除。

\n\n

变基的一大缺点是每个使用过的人都feature必须转而使用新的变基feature提交副本。如果该功能已准备就绪,并且将删除其名称,那么通常很容易。但这绝对符合我喜欢使用的关于 Git 的另一条规则: 只有当使用这些提交的每个人都同意迁移到新的和改进的副本时,才用新的和改进的副本替换提交。 该协议通常应提前达成。

\n\n
\n\n

7 Rebase 有一组令人眼花缭乱的选项,包括-i-k-m-s,其中一些选项导致它实际使用git cherry-pick。其他方法在内部使用git format-patchand git am,但旨在产生相同的结果。

\n\n

8还有另一个 Git 功能和命令,git rerere带有启用选项,允许 Git 为您执行此操作,但请始终记住,Git 并不智能,它只是应用简单的文本替换规则。如何rerere工作以及可能出错的地方相当棘手,我不会在这里详细介绍。

\n\n

9请注意,如果ORIG_HEAD后续操作写入,您还可以使用feature@{1}find S。如果您遇到需要知道 的哈希 ID 的复杂情况S,请将其复制粘贴到某处,或者创建一个临时分支或标记名称来保存它。

\n\n

在实践中,当我重新构建一个复杂的功能时,我喜欢做的是:

\n\n
git checkout feature\ngit branch feature.0    # or .1, .2, etc if I already have .0, etc\ngit rebase master\n
Run Code Online (Sandbox Code Playgroud)\n\n

feature.0现在我有了记住原始分支提示提交的名称。

\n


eft*_*ft0 1

没有理由害怕...很多人都用git rebase -i. 我个人遵循一条不会留下任何东西并且不涉及变基的替代路径:

git checkout my-feature-branch
git pull # merge changes from upstream, do _not_ rebase.
# correct conflicts if they show up and finish merge
# after merge is finished/committed the only differences between your branch and upstream are related to _your_ feature and so....
git reset --soft the-upstream-branch # set branch pointer to upstream branch, all differences are set on index ready to be committed
git commit -m "the feature in a single revision"
Run Code Online (Sandbox Code Playgroud)