当 MR 中没有任何更改时,为什么 GitHub 需要我对合并请求进行变基

M T*_*son 2 git merge github rebase

在使用 GitHub 时,我不明白我是否提出合并请求,三天后当我去批准 MR 时,它说我需要重新建立 MR 基础?谁能解释一下这让我发疯。我是 GitHub 的新手,所以请任何帮助都会很有帮助,谢谢。

tor*_*rek 7

Git\xe2\x80\x94 和 GitHub\xe2\x80\x94 不一定需要这个。需要它的是人类,和/或人类强加的规则。以下内容很长,但我建议值得一读。

\n

Long:rebase 是关于什么的

\n

要有效地使用 Git 和 GitHub,您应该了解以下内容:

\n
    \n
  • Git 没有拉取请求或合并请求。这些是附加组件,由各种托管站点(GitHub、Bitbucket、GitLab 等)提供。

    \n
  • \n
  • GitHub 调用他们的附加组件拉取请求;是 GitLab 调用他们的合并请求

    \n
  • \n
\n

这些都是相对较小的,但由于 Git 术语已经非常混乱,所以最好尽可能清晰。

\n

无论他们如何称呼它们以及如何在内部和外部实现它们\xe2\x80\x94,这些托管站点之间也有所不同\xe2\x80\x94这些都构建在一些基本的 Git 技术之上。掌握这些会有帮助。以下是您需要了解的信息:

\n
    \n
  • Git 是围绕提交构建的。提交是Git存在的理由。除了为承诺服务之外,没有什么真正重要的。

    \n
  • \n
  • 每个提交都有一个唯一的编号,通常以十六进制表示,看起来像一串丑陋的字母和数字。重要的是,该数字就是提交,这就是为什么它需要唯一的原因。两个 Git 在互相交谈时,只会交换原始数字,看看他们是否都有提交。如果不是,一个 Git 可能必须将提交发送到另一个 Git。(Git 存储库“喜欢”向自身添加新提交,并且“不喜欢”忘记任何提交。1 我们将这些数字称为哈希 ID

    \n
  • \n
  • 每个提交都包含两件事:

    \n
      \n
    • 每次提交都有某个项目的所有文件的快照。这里的内部存储格式很复杂,并且并不真正相关,但值得知道的是(1)它充当快照,并且(2)其中的所有文件都针对任何存在于其中的所有副本进行了重复数据删除。其他提交,因此大多数提交大多包含与大多数其他提交相同的文件这一事实不会使存储库膨胀太多。

      \n
    • \n
    • 每个提交都有一些元数据,或者有关提交本身的信息:例如,是谁以及何时进行的。元数据包括您的姓名和电子邮件地址(来自user.nameuser.email设置)以及您输入的任何日志消息。从 Git 的角度来看,\xe2\x80\x94 至关重要\xe2\x80\x94 每个提交都包含原始提交哈希 ID(一些早期提交的集合。

      \n
    • \n
    \n
  • \n
  • 任何提交的所有部分一旦创建,都是只读的。原因之一是提交的哈希 ID 只是提交内容的加密校验和。2 如果您从 Git 数据库中获取 commit\xe2\x80\x94 或任何内部对象\xe2\x80\x94,进行一些更改,然后将结果写回,您只会得到一个具有不同哈希 ID 的新对象。原来的物体仍然存在。

    \n
  • \n
\n

最后一点使得 Git 存储库通常只能追加(这解释了添加新提交的拟人化“喜欢”)。可以采取一些“不够好”的现有提交并将它们复制到新的和改进的提交,停止使用旧的提交。如果每个Git 存储库都这样做,旧的提交最终可能会“消失”。3事情就是这样 git rebase

\n
\n

1不要将计算机拟人化\xe2\x80\x94他们讨厌那样!

\n

2这意味着每次提交都必须是唯一的。例如,存储的先前提交的哈希 ID 以及时间戳可以在此处提供帮助。这也意味着 Git 最终一定会失败:鸽巢原理告诉我们,任何哈希方案最终都会发生冲突。哈希 ID 的大小决定了Git 失败的速度。经过精心设计,这种情况需要几十年的时间才能发生。不过,对 SHA-1 的恶意攻击可能会导致早期失败,并且存储库的大小总体上在增长,这两者都导致 Git 最终从 SHA-1 迁移到 SHA-256。

\n

3细节比较复杂,这里不再赘述。

\n
\n

在 Git 存储库中工作

\n

当人类在 Git 存储库中工作时,过程通常是这样的:

\n
    \n
  1. 如果需要,克隆一些现有存储库,或者,如果需要更新,然后使用现有克隆。
  2. \n
  3. 做一些工作。进行新的提交,因为 Git 就是关于提交的。
  4. \n
  5. 根据需要进行测试和返工。
  6. \n
  7. 宣布准备就绪。
  8. \n
  9. 完成任何审查流程;这可能会使人返回到步骤 2 或 3。
  10. \n
  11. 纳入工作。
  12. \n
\n

如果任何时候只有一个人在做任何工作,并且没有发生返工或循环步骤,那么这个过程就非常简单。唯一的松鼠部分发生在步骤 1 和 6。如果我们忽略这些,我们会看到一个漂亮、简单的过程,如下所示。在这里,我将使用单个大写字母来代表其哈希 ID 来绘制提交:

\n
... <-F <-G <-H   <--main\n
Run Code Online (Sandbox Code Playgroud)\n

现在,哈希 ID 为 的提交H是or分支上的最新提交。Git 本身根本不关心分支名称:它只是使用它们来查找提交。具体来说,分支名称保存一次提交的哈希 ID,而该一次提交我们\xe2\x80\x94 或 Git\xe2\x80\x94 称为“分支的一部分”的最新提交。mainmaster

\n

由于每个提交都保存了较早提交\xe2\x80\x94 或有时两个较早提交的哈希 ID;我们稍后会看到\xe2\x80\x94commitH包含早期提交的原始哈希ID G。我们说 commitH 指向commit G,因此上图中有向后的箭头。

\n

提交H还包含每个文件的完整快照。这些是我们运行时要处理的文件git checkout main。请注意,我们处理的文件位于我们的工作树中。它们是从提交中复制出来的:在提交中,它们采用某种特殊奇怪的仅限 Git 的格式,经过压缩和重复数据删除,计算机上的大多数软件都无法使用它们。

\n

Git使用name中存储的哈希 ID找到提交。这就是(或者,在 Git 2.23 或更高版本中)从中获取所有文件的方式。这就是如何向您显示有关提交的信息:它使用名称来查找哈希 ID,然后使用哈希 ID 在所有 Git 提交的大型数据库中查找内部提交,并且-其他支撑物体。H maingit checkout maingit switch maingit logHmain

\n

由于提交H存储了提交的G哈希 ID,GitG也可以使用它来提取提交的文件,并且可以将快照与 中的快照进行比较。通过这样做,Git 可以向我们显示哪些文件(如果有)发生了更改,以及进行了哪些更改,即使只是一个快照。GHH

\n

当然,提交G是一次完整提交,具有先前的提交哈希 ID F,因此 Git 可以加载这两个提交F,并G使用它来显示提交中发生的更改G。该命令还可以显示提交git log日志消息,并从提交中找到哈希 ID 。GH

\n

当然,提交F是完整提交,因此 Git 可以继续这样做。它可以一直保持到第一次提交。 提交在一方面是特殊的:它指向任何较早的提交。Git 知道,在到达该提交后,停止后退。

\n

所以,Git 是逆向工作的。但是进行新的提交又如何呢? 这实际上也非常简单。不过,在进行新的提交之前,让我们创建一个新的分支名称,如下所示:

\n
...--F--G--H   <-- develop, main\n
Run Code Online (Sandbox Code Playgroud)\n

Git 要求我们选出一个分支名称作为当前分支。我们用git checkout或 来完成此操作git switch。为了记住我们选择了哪一个,我们将HEAD在括号中绘制特殊名称,附加到这些分支名称之一:

\n
...--F--G--H   <-- develop, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

on branch main正如我们git status将要说的,我们在这里使用 commit H。如果我们git checkout develop,我们得到:

\n
...--F--G--H   <-- develop (HEAD), main\n
Run Code Online (Sandbox Code Playgroud)\n

现在我们会on branch developgit status,但仍然使用 commitH。请注意,此时每个提交都在两个分支上。

\n

现在,我们以通常的方式修改工作树中的一些文件(它们是实际上不在Git中的普通文件),然后运行git add以准备提交。略过一些其他相当重要的内容,这替换了 Git 暂存区域中的文件副本。这些额外的副本是 Git 用于进行新提交的内容,并且暂存区域实际上具有 Git 从 commit 复制出来的相同文件副本H。此时它们只是处于准备提交、压缩和预先去重的形式。使用git add告诉 Git 用新副本替换其中一些文件:Git 会及时压缩和删除重复文件git add,以便可以立即git commit使用 Git 索引中的任何内容。

\n

最后,我们运行git commit。这:

\n
    \n
  • 从我们这里获取元数据:要放入新提交的日志消息、当前的设置user.nameuser.email。日期和时间戳是“现在” ,新提交的父提交是当前提交,如当前分支名称所示。在本例中,这就是 commit H

    \n
  • \n
  • 现在从 Git 索引中的任何内容创建永久快照。由于我们过去常常git add更新这些文件,因此这是正确的快照。

    \n
  • \n
  • 将所有这些写出作为新的提交。这将为新的、唯一的提交获取一个新的、唯一的哈希 ID。提交对象进入大数据库:

    \n
    ...--F--G--H\n            \\\n             I\n
    Run Code Online (Sandbox Code Playgroud)\n

    请注意 new 如何I指向现有的 commit H。(我不能在这里画大箭头,所以我已经转到线条了。 但是H不能指向IH是很久以前制作的,并且不能更改。所以I必须指向回H。)

    \n
  • \n
  • 最后,Git 使用它的特殊技巧:它将提交的哈希 ID 写入当前分支名称

    \n
  • \n
\n

最后一个技巧就是让我们:

\n
...--F--G--H   <-- main\n            \\\n             I   <-- develop (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

这让我们可以一次添加一个新提交,每个新提交都会推进当前分支develop,因为那是HEAD附加的位置)。如果我们再添加一个提交,我们会得到:

\n
...--F--G--H   <-- main\n            \\\n             I--J   <-- develop (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

如果这里一切看起来都很好,我们现在可以简单地git checkout main告诉 Git:提交J很棒,将其用作太中的最后一次main提交。跳过详细信息\xe2\x80\x94Git 使用所谓的快进合并\xe2\x80\x94 来执行此操作,结果是:

\n
...--F--G--H--I--J   <-- develop, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

现在所有提交都在两个分支上,并且删除任一名称\xe2\x80\x94 是安全的,另一个将找到所有提交。

\n

请注意,之前删除 name 是安全的(在某种意义上)mainJ我们可以通过从 开始并向后工作来找到所有提交。这就是分支名称的要点:它们为我们提供了起点和向后工作的位置。保留的唯一原因是专门记住一段时间,但这是一个相当不错的理由\xe2\x80\x94,特别是如果我们认为这些提交毕竟是糟糕的提交并想将它们扔掉。 mainHI-J

\n

扔掉旧的提交

\n

假设我们确实认为那I-J很糟糕。这是扔掉它们而不是合并它们的一种方法:

\n
git checkout main\ngit branch -D develop\n
Run Code Online (Sandbox Code Playgroud)\n

第一步,如果我们想要进行快进合并,我们也会这样做,得到:

\n
...--G--H   <-- main (HEAD)\n         \\\n          I--J   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们git log现在运行,我们不会看到commits I-J。我们必须跑去git log develop他们,用名字develop来寻找J

\n

第二命令告诉 Git 强制删除名称develop\xe2\x80\x94,因为如果不强制 Git,它会说不:这会让我们失去对 commits 的访问权限I-J。通过删除 name develop,我们最终得到:

\n
...--G--H   <-- main (HEAD)\n         \\\n          I--J   [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n

通过删除name,我们再也找不到提交,并且只要我们还没有将它们发送到任何其他Git,我们就永远不会再被它们打扰\xe2\x80\x94。

\n

我们现在可以develop再次创建,再次指向H,并再次尝试我们的开发工作,这一次知道我们做错了什么:

\n
          K--L   <-- develop (HEAD)\n         /\n...--G--H   <-- main\n         \\\n          I--J   [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n

当我们合并(新的、好的)提交时,I-J如果愿意,我们可以直接调用它们,就好像废弃的提交完全消失了一样。他们的真实姓名是一些又大又难看的哈希ID;毕竟,我们只是编造这些单字母名称。

\n

如果我们是唯一做任何工作的人,那就太好了,但在很多情况下这是不现实的。

\n

并行开发

\n

让我们从两个用户开始。我将在这里使用标准的“爱丽丝和鲍勃”,尽管显然这个习语由于某种原因已经不再受欢迎。每个人都制作自己的克隆,这样每个人都有自己的分支名称。这进入了一个小范围的讨论:

\n
    \n
  • 每个存储库共享提交。
  • \n
  • 但每个存储库都有自己的 分支名称。
  • \n
\n

在 Alice 的系统上,她得到:

\n
...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

在鲍勃的身上,他得到:

\n
...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

当 Alice 进行两次新提交(在main分支名称或任何其他分支名称上)时,她的提交将获得唯一的哈希 ID:

\n
          I--J   <-- alice\n         /\n...--G--H\n
Run Code Online (Sandbox Code Playgroud)\n

同时,当 Bob 进行两次新提交时,他的提交也会获得唯一的哈希 ID:

\n
...--G--H\n         \\\n          K--L   <-- bob\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们将所有这些提交合并到一个存储库中,并使用名称alicebob查找最后的提交,我们会得到以下图片:

\n
          I--J   <-- alice\n         /\n...--G--H\n         \\\n          K--L   <-- bob\n
Run Code Online (Sandbox Code Playgroud)\n

(也许,main指向H\xe2\x80\x94,尽管我们不需要名称H,因为我们可以通过从任一分支尖端开始向后工作来找到它)。

\n

鉴于并行开发的存在,我们现在面临一个问题:如何将这些并行开发线连接起来?

\n

合并(真正的合并)

\n

实现此目的的一种方法是使用 Git 合并工作的功能。我们将所有提交获取到某处的某个 Git 存储库中,并使用如上所述的分支名称来查找它们。然后我们选择两个分支之一来签出/切换到,并git merge与另一个一起运行:

\n
git checkout alice\ngit merge bob\n
Run Code Online (Sandbox Code Playgroud)\n

例如。

\n

Git 的合并引擎现在执行我喜欢将合并作为动词进行的操作:查找自某个共同起点以来的更改的操作。从图中可以明显看出共同的起点:它是 commit H

\n

Git 现在将使用其比较软件\xe2\x80\x94 git diff,或多或少\xe2\x80\x94 来将commit 中的快照与 commit 中的快照进行比较,以查看 Alice 更改了哪些文件,以及 Alice 对这些文件做了哪些更改。Git 还将使用与进行比较,以查看 Bob 更改了哪些内容。然后,对于每个文件:HJgit diffHL

\n
    \n
  • 如果没有人更改文件,我们只使用原始文件。
  • \n
  • 如果一个人更改了文件而另一个人没有更改,我们将采用更改后的文件。
  • \n
  • 如果两个人都更改了同一个文件,我们会让 Git 努力合并他们的更改。
  • \n
\n

Git 的组合是通过一个简单而愚蠢的算法完成的,它只查看逐行的更改。如果更改的行没有“接触”或“重叠”,Git 将接受这两个更改。如果它们确实接触或重叠,Git 通常会声明合并冲突并强制运行人员git merge来清理混乱。这里有很多特殊情况,但如果 Alice 和 Bob 在系统的不同部分工作,Git 通常能够自行完成所有工作组合。

\n

由于我们在这里并没有真正git merge正确地进行介绍,所以我们假设 Git 认为一切顺利,以便 Git 为您做出自己的新提交。Git从公共起点\xe2\x80\x94(Git 称之为合并基础\xe2\x80\x94in commit )将合并的更改应用到快照,这会保留两组更改。Git 将所有生成的文件写入您的工作树和 Git 自己的索引/暂存区域。然后,Git 从这些文件中进行新的提交:H

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

由于我们运行git checkout alice开始此操作,因此我们位于分支alice,因此新合并提交的哈希 ID 进入名称alice。生成的提交具有快照\xe2\x80\x94,就像通过将组合更改应用快照所做的任何提交\xe2\x80\x94 一样H。它有元数据,就像任何普通的提交一样,表明我们刚刚进行了此提交。此提交的唯一特别之处在于,它不是指向commit I,而是指向分支提示提交IK.

\n

现在我们可以删除任何不需要的名称。这里我们不需要的名称是:我们可以通过向后工作bob找到提交。Git 将自动遵循两个向后指向的箭头,向后执行两次提交。KM

\n

这是真正的合并,也是组合工作的一种方式。Git 可以做到这一点;GitHub 可以做到这一点;只要不存在合并冲突,GitHub 拉取请求就可以通过这种流程进行处理。但有些不喜欢这样做。

\n

变基而不是合并

\n

假设我们是 Alice,我们有这样的情况:

\n
          I--J   <-- alice (HEAD)\n         /\n...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

但鲍勃首先将他的提交添加到某个存储库中:

\n
...--G--H--K--L   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

我们现在可以git fetch针对另一个存储库\xe2\x80\x94运行,我们将origin在此处调用它\xe2\x80\x94来获取新的提交K-L。这是我们将在自己的本地存储库中看到的内容:

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

如果我们有一个“禁止合并”规则\xe2\x80\x94谁知道为什么我们有这个规则4 \xe2\x80\x94我们必须采取我们完美的I-J提交并“改进”它们,通过添加L.

\n

要手动执行此操作,我们需要使用git cherry-pick两次新的临时分支名称,然后(例如)更改分支名称。但该git rebase命令可以一次性为我们所有人完成此操作:

\n
git rebase origin/main\n
Run Code Online (Sandbox Code Playgroud)\n

变基操作复制某些提交集的效果。为此,它必须使用 Git 的合并机制,即合并作为动词的想法部分。该git cherry-pick命令实现了这一点,一次提交一个,并重复git merge运行git cherry-pick5 一旦准备好正确的快照,每个cherry-pick步骤都会提交该快照,重新使用原始提交的日志消息,但作为常规单父提交,而不是合并提交。所以一旦这个阶段完成,我们就有了:

\n
          I--J   <-- alice\n         /\n...--G--H   <-- main\n         \\\n          K--L   <-- origin/main\n              \\\n               I\'-J\'  <-- HEAD\n
Run Code Online (Sandbox Code Playgroud)\n

其中I\'是 Git 的自动提交副本IJ\'是 Git 的自动提交副本J。该图还说明了 rebase 在内部使用的一个技巧:它以 Git 所谓的分离 HEAD模式运行,以避免必须编写临时分支名称。6

\n

但是,一旦完成所有副本,Git 就会使用另一个内部命令强制原始分支名称指向最后复制的提交。然后它重新附加HEAD,这样我们就有:

\n
          I--J   [abandoned]\n         /\n...--G--H   <-- main\n         \\\n          K--L   <-- origin/main\n              \\\n               I\'-J\'  <-- alice (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,这与我们故意丢弃从未发送给其他人的提交时所发生的情况相似。

\n

(这里的一个不幸的副作用是,通常,在这种工作流程中,我们已经将这些提交发送到某处进行审查。我们将在下一节中讨论这一点。)

\n
\n

4在我看来,这并不是一个好的规则。我之前在项目\xe2\x80\x94中遵循过它,这并不是一个可怕的规则\xe2\x80\x94,但我认为只是盲目地说“不合并”是错误的。尽管如此,人们还是喜欢它。

\n

5事实上,git rebase这是一个极其复杂的命令,可以通过多种不同的方式完成其工作。在现代 Git 中,它现在默认在内部使用git cherry-pick。在稍旧的 Git 版本中,您需要-m-i或类似的选项才能使其使用git cherry-pick. 其他选项添加特殊功能,而 rebase 已经具有许多特殊功能,因此某些选项禁用这些功能。但大多数情况下,它是将一些现有的不太好的提交复制到新的和改进的提交,这也是问题所在git cherry-pick,因此它们密切相关。

\n

6如果 rebase 必须停止以获得合并冲突的帮助,或者如果您使用变git rebase -i体并使其故意停止,则此分离的 HEAD 模式实现细节会“泄漏”:当 rebase 在操作中间停止时,您\仍处于分离头模式。您必须告诉 Git 恢复变基或终止变基,才能退出分离头模式。这会变得混乱,你应该小心不要使用git checkout来退出分离的 HEAD 模式。

\n
\n

GitHub 分支世界中的变基

\n

以上所有都是我们可以在基础 Git 中完成的事情。然而,GitHub 和其他托管网站添加了一系列功能,希望我们足够喜欢这些功能,从而能够实际支付这些托管网站上的服务费用。

\n

第一个 GitHub 特定功能是 GitHub fork。GitHub 上的分叉就像一个克隆,但有两个变化:

\n
    \n
  • 通常,如果我们使用git clone克隆存储库:

    \n
    git clone -b foo ssh://git@github.com/user/repo.git\n
    Run Code Online (Sandbox Code Playgroud)\n

    我们从该存储库获取所有提交但没有获取它们的任何分支。我们的最终结果是使用我们在这里为参数指定的名称git clone在本地创建一个-b分支名称。如果我们没有给出-b参数,我们的 Git 会询问他们(GitHub 的)Git 他们推荐的分支名称,然后我们的 Git 根据他们的推荐创建该分支。

    \n

    我们的 Git 对它们的分支名称所做的是将它们全部更改为远程跟踪名称: theirmain变成 our origin/main,theirdevelop变成 our origin/develop,theirfeature/short变成 our origin/feature/short,等等。

    \n

    Git 这样做是因为我们的分支名称是我们自己的,可以随心所欲地使用。Git 将所有名称复制回来是可以的origin/*,但无论如何,Git 仍然需要这种“重命名它们的分支,并使用我们自己的名称”的技巧,这样我们在提交时就不会丢失新的提交。从 他们的Git获取更新。

    \n

    不过,通过 GitHub 分叉,我们的 Git 在分叉中为原始 Git 中的每个分支名称创建一个分支名称。这是无害的,并且在某些方面是好的(参见上一段)。

    \n
  • \n
  • 并且,在幕后,GitHub共享底层提交和其他内部对象,以便节省自己服务器上的空间\xe2\x80\x94这总是可以的,因为每个对象都有自己唯一的哈希 ID\xe2\x80\x94为我们记住他们的存储库,以便我们可以发出 Pull 请求。

    \n
  • \n
\n

这种提出拉取请求的能力是最畅销的功能之一。(GitHub 管理评论、问题等的能力是另一个。这也是一个附加组件,不存在于基础 Git 中。)

\n

拉取请求的核心只是一种方式,我们可以在单击按钮时向所使用的存储库的所有者发送电子邮件或其他警报,FORK让他们知道,在我们的 GitHub 分支中,我们添加了我们现在提供给他们查看和/或添加到他们的克隆中的一些提交。由他们决定是否按原样接受我们的提交\xe2\x80\x94,这要求他们使用原始 Git,或 GitHub 按钮MERGE\xe2\x80\x94,或者对它们进行某种更改,或者要求我们对它们进行更改,或者其他什么。

\n

如果他们确实要求我们对提交进行更改,我们可以这样做,然后将git push --force提交发送到我们自己的 GitHub 分支。

\n

我之前提到过 Git“不喜欢”放弃提交。当我们使用git rebase 或任何其他过程用新的和改进的替换来替换现有提交时,如果我们曾经将原始版本发送到任何地方并且现在发送替换,我们将要求其他 Git 存储库提供把原件拿出来,用替换件代替。

\n

当我们进行替换时,如果我们git rebase至少使用了的话,我们的 Git 就知道我们正在这样做。(如果我们手动执行此操作,我们可能必须强制Git 接受我们的替换。)GitHub 上的 Git 不知道我们新的和改进的替换提交是 的结果git rebase,因此我们必须强制 Git GitHub 将它们作为替代品。所以我们必须git push --force到我们的叉子上。

\n

GitHub 使用的附加软件会注意到这种强制推送到我们发出 Pull 请求时使用的分支的情况,并会在其他人查看我们的 PR 时自动更新他们看到的PR 。

\n

这并没有告诉我们为什么必须变基。它不能,因为 Git 本身以及 GitHub 作为一个附加组件,一开始就不需要变基是某个地方的一些人提出了这个要求。由于很多人喜欢这个过程(无论出于何种原因),GitHub 甚至可能在今天或明天自动化该需求。

\n

无论如何,如果您做到了这一点,您现在就知道如何做到这一点,并且任何底层软件都不需要它。但还有一件事需要考虑。

\n

无法自动化的合并无法在 GitHub 上完成

\n

(这实际上是夸大其辞,因为 GitHub 一直在添加新功能。但总的来说确实如此,而且它会影响 merge 和 rebase。)

\n

当 Git 可以自行合并更改时,它就会这样做,GitHub 对 Git 的使用也会这样做。当 Git无法自行合并更改时,它需要寻求某人的帮助。

\n

如果合并存在合并冲突,Git 将停止并在工作树中留下混乱。现在你的工作就是解决这个烂摊子。GitHub上的Git 存储库没有工作树(由于各种内部原因),因此没有地方可以修复混乱的情况。

\n

因此,在 GitHub 让您发出 Pull 请求之前,他们首先运行测试合并。此测试合并要么成功,因为 Git 可以自己完成所有操作,要么失败,因为它不能。如果测试合并失败,GitHub 会让您知道您的 PR 有冲突。

\n

现在,假设您有一个分叉,以及笔记本电脑上存储库的本地副本,并且您做了一些工作并进行了一些提交,它们都已准备就绪:

\n
          I--J   <-- alice\n         /\n...--G--H   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

您将其发送到您的 GitHub 分支,以便您的 GitHub 克隆拥有一个alice以提交作为其最后一次提交的分支J。您分叉的存储库仍然有提交H作为其最后一次提交 main并且您可以创建 PR。

\n

我们恼人的同事鲍勃来了,他做出了一些承诺:

\n
          I--J   <-- alice\n         /\n...--G--H   <-- main\n         \\\n          K--L   <-- bob\n
Run Code Online (Sandbox Code Playgroud)\n

Bob 将这些提交添加到他们的 中main,并且他们尚未接收您的工作:

\n
          I--J   <-- alice\n         /\n...--G--H--K--L   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

之前您的工作(扩展其提交)之间没有冲突H,但现在存在冲突,因为鲍勃触及了相同文件相同行并进行了不兼容的更改。

\n

GitHub 系统无法再将您的工作I-J与其分支提示结合起来L。用 GitHub 的话说,你的 PR 已经变得矛盾了。他们(GitHub)在K-L向他们的(GitHub 的其他人克隆,您从中分叉)添加提交时注意到了这一点main

\n

GitHub 会通知您,以便您可以完全撤回您的 PR,或者对合并冲突采取措施。 由于基本的 Git 要求,您不必重新调整 PR 。确实必须解决由于基本 Git 要求而导致的合并冲突,也许J可以通过添加一个新的提交来很好地与L. GitHub 附加组件可能会更改此情况,但冲突本身会导致需要更新 PR。所需的更新不一定是基。

\n