我的分支在“挤压和合并”后发生了分歧。我该如何解决这个问题?

Seb*_*min 2 git merge github squash pull-request

我进行了“挤压并合并”来关闭 PR。虽然现在所有的变化都呈现出来master,但仍然说分支已经分叉,前后仍然有变化。

This branch is 1 commit ahead, 28 commits behind develop. 
Run Code Online (Sandbox Code Playgroud)

我该如何解决?后面的 28 个提交是在“压缩和合并”期间被压缩的。我已经通过https://help.github.com/en/github/administering-a-repository/about-merge-methods-on-github#squashing-your-merge-commits,但找不到任何连接。

我假设(错误地?)“挤压并合并”会进行快进合并以防止发散。我不希望所有提交都从develop移动到master,这就是为什么我进行了“压缩和合并”,而不是变基。我想我错过了一些关于挤压和合并的事情。

我已经在 SO 上遇到了类似的问题,但最终建议在合并到master. 但我不想失去历史develop

tor*_*rek 10

\n

我该如何解决?

\n
\n\n

这只是可以修复的。通常您现在应该完全删除该分支。然后,您可以创建一个分支,即使该分支仍名为develop. 即使名称相同,它也是一个不同的分支。

\n\n

这一系列命令可能会修复它,但要非常小心并确保您理解每一个命令:

\n\n
git checkout master\ngit reset --hard develop\ngit push --force-with-lease origin master\n
Run Code Online (Sandbox Code Playgroud)\n\n

git reset --hard命令将丢弃任何未提交的工作,因此在使用它时要格外小心。

\n\n

我将在下面给出一些替代方案。有很多选项,具有不同的效果,特别是对于可能使用 GitHub 存储库的任何其他人。如果是唯一使用 GitHub 存储库的人,您可以做任何您喜欢的事情。

\n\n

无论如何,请记住:这是挤压合并的目标:杀死分支。这个想法是,在这次压缩和合并之后,您将删除分支名称并永远放弃所有 28 个提交。压缩和合并创建了一项提交,其中包含与之前 28 项提交相同的工作,因此一项新提交是这 28 项提交的新改进替代品。

\n\n

要了解您在做什么,请继续阅读。

\n\n

要真正“掌握”Git,你需要画图

\n\n
\n

我假设(错误地?)“挤压和合并”会进行快进合并以防止发散。我不希望所有提交都从develop移动到master,这就是为什么我进行了“压缩和合并”,而不是变基。我想我错过了一些关于挤压和合并的事情。

\n
\n\n

是的。GitHub 也在这里抛出了一个额外的问题\xe2\x80\x94,或者可能会消除你想要的问题这取决于你如何看待它。我们首先需要一点点。

\n\n

Git 就是关于提交。这些提交形成一个特别是A循环DAG。它的工作方式实际上非常简单:

\n\n
    \n
  • 每个提交都有一个唯一的哈希 ID。该哈希 ID 实际上是提交的真实名称。

  • \n
  • 每个提交都由两部分组成:其数据\xe2\x80\x94所有文件的快照\xe2\x80\x94和一些元数据,有关提交本身的信息,例如谁做出了它(您的姓名和电子邮件地址),时间(日期和时间戳)以及原因(您的日志消息)。大多数元数据只是为了在您查看提交时显示,但有一条信息对于 Git 本身至关重要:每个提交都会列出其父提交的原始哈希 ID 。

  • \n
\n\n

大多数提交只有一个单亲。当某些内容包含提交的哈希 ID 时,我们说该内容指向该提交。因此提交指向其父级。如果我们有一系列连续的提交,我们可以这样绘制它们:

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

其中大写字母代表实际的哈希 ID。最新提交是 commit H。它指向它的父级,它就在它之前: commit G。提交G指向其父级F,等等。

\n\n

在 Git 中,分支名称只是指向最新提交的名称:

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

当我们创建一个新分支时,我们真正要做的是告诉 Git:创建一个指向某些现有提交的新名称。因此,如果我们现在创建一个分支develop,我们会得到:

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

现在我们需要知道我们实际使用的名称HEAD,因此我们将特殊名称附加到该名称上:

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

当我们进行提交时,我们只需让 Git 写出提交\xe2\x80\x94,这会使用快照计算新提交的唯一哈希 ID\xe2\x80\x94,并将其父集设置为当前提交H在片刻):

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

现在名称发生的情况也非常简单:HEADGit 将新提交的哈希 ID 写入附加的名称。所有其他的:什么也没有发生。所以我们的新图是:

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

当我们添加更多提交时,他们只会不断增长分支:

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

(这里develop3 aheadmaster。)

\n\n

还有另一件需要意识到的关键事情:提交GH,以及在 上的那些之前的所有提交master也都在 上develop 换句话说,提交通常发生在多个分支上。(这对于 Git 来说是一个奇怪的事情:许多其他版本控制系统并不以这种方式工作。)

\n\n

真正的合并和快进

\n\n

此时,我们可以git merge使用以下命令在我们自己的存储库(但不能在 GitHub 上)本地运行 , :

\n\n
git checkout master; git merge develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

Git 会采用快捷方式 \xe2\x80\x94你提到的快进。我们稍后再讨论这个问题。

\n\n

或者,我们可以运行git merge --no-ff develop,强制 Git 进行真正的合并。 这就是 GitHub 上的“合并”按钮的作用,所以让我们看一下。 “真正的”合并总是会进行新的合并提交。如果两个分支有分歧,则需要真正的合并例如,如果我们有:

\n\n
       o--o   <-- branch1\n      /\n...--o\n      \\\n       o--o   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我们想合并它们,我们将被迫使用真正的合并。那不是我们所拥有的;我们有:

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

(记住,我们正在master:我们做了一个,git checkout master所以H当前提交。 K是最后一次提交develop。)

\n\n

GitHub 只是强制所有内容进行真正的合并,包括我们自己的简单合并。要进行真正的合并,Git 会找到一个合并基础提交\xe2\x80\x94(在本例中为提交H\xe2\x80\x94),并将该合并基础进行两次比较,一次与当前提交比较H,一次与提交比较K

\n\n

显然, commitH与 commit 匹配H。因此,我们这边没有任何需要继续推进的改变。我们只想要他们所做的任何改变,从HK。结果将与 中的快照相同K。但我们正在强制合并,因此 Git 会进行新的合并提交,我将要求这样Mmerge

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

\xe2\x80\x94的特殊之处在于M它不仅有一个父代,而且有两个父代,这使得它成为一个合并 \xe2\x80\x94。它的第一个父级是 commit H,这就是我们刚才所在的位置。它的第二个父级是 commit K。它的快照H是我们在-vs- \xe2\x80\x94no work at all\xe2\x80\x94 上所做的工作与他们在-vs-H上所做的工作相结合的结果,因此新合并提交的快照与提交匹配\ 的快照,但最终结果是这个新的合并提交。HKK

\n\n

如果我们允许 Git 进行快进而不是合并,这就是我们得到的结果:

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

(这使我们能够消除绘图中的问题,尽管我没有打扰)。Git 将此称为快进合并,即使没有实际发生合并。不幸的是GitHub 的按钮不允许我们进行快进合并

\n\n

挤压合并是真正的合并,但没有第二个父级

\n\n

如果我们进行真正的合并,我们将得到这个图:

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

提交M将具有与提交相同的快照,K并且将有两个父级,H并且K.

\n\n

进行挤压合并,我们得到这个图:

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

CommitS与 commit 具有相同的快照K,但只有一个父级H. 这里的想法是,您想要一个普通的提交,它与您在 上所做的所有提交具有相同的效果develop。这个单一提交是完成这项工作的新的和改进的方式。旧的方式\xe2\x80\x94旧的I-J-K提交\xe2\x80\x94不再有用,应该被放弃;最终,30 天或更长时间后,您的 Git 将会真正删除它们。所以你只需运行name 的强制删除develop,生成:

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

如果您愿意,现在可以再次创建该名称 develop,但这次指向 commit S

\n\n
...--G--H--S   <-- develop, master (HEAD)\n         \\\n          I--J--K   [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n\n

分支名称的目的是让您(和 Git)找到提交

\n\n

请注意,当我们操作图表以添加提交时,我们会移动分支名称。分支名称始终指向最新的提交。这就是 Git 定义工作的方式。

\n\n

然而,我们可以强制分支名称移动到我们喜欢的任何地方。如果我们检查了一个特定的分支,就像在这种状态下一样:

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

我们可以使用git reset --hard来切换提交,并且可以说,将提交S推开:

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

请注意,提交S仍然存在。只是从 name 开始master,我们找到 commit H,然后我们向后工作并找到 commit G,等等。我们永远不会前进,所以我们无法承诺S。它已被放弃,就像I-J-K我们删除 name 时的提交一样develop

\n\n

如果您将提交的原始哈希 ID 保存在某处\xe2\x80\x94,例如,将其写在纸上或白板上,或者将其剪切并粘贴到文件\xe2\x80\x94中,您可以使用该哈希稍后直接 ID,让 Git 找到该提交。如果它仍在存储库(默认情况下至少保留 30 天),您可以通过这种方式找到它。但你不会用最普通的方式找到它git log和其他 Git 命令找到它。

\n\n

请注意,如果一次提交涉及多个分支\xe2\x80\x94e.g.,如果我们有:

\n\n
...--P   <-- branch1\n      \\\n       Q   <-- branch2\n        \\\n         R   <-- branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们删除名称branch2\xe2\x80\x94 它可能仍然可以找到。删除名字的结果 branch2是:

\n\n
...--P   <-- branch1\n      \\\n       Q--R   <-- branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

毕竟。Q不管怎样,提交仍然存在,但这一次,我们可以找到它,从R并向后工作来找到它。

\n\n

这就是为什么绘制图表或图表的一部分很重要。您可以找到的提交提交也是您明天也能找到的提交\xe2\x80\x94假设您不移动名称,也就是说。

\n\n

到目前为止,这给了你两个选择

\n\n

您可以通过删除名称来保留您的挤压合并提交并忘记其他提交develop

\n\n
...--o--o--S   <-- master\n         \\\n          o--o--...--o   [was develop; 28 commits, all abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者你可以形成你自己的本地 Git 图,如下所示:

\n\n
          S   <-- origin/master\n         /\n...--o--o   <-- master (HEAD)\n         \\\n          o--o--...--o   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,您master在 commit 之前一步标识了提交S。nameorigin/master是 Git 的 GitHub 本地副本master,指向 commitS

\n\n

如果你master 已经看起来像这样,那么你已经准备好了。如果你的master看起来像这样:

\n\n
          S   <-- master (HEAD), origin/master\n         /\n...--o--o\n         \\\n          o--o--...--o   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

你会跑git reset --hard HEAD~1把它向后移动一个,使它看起来像第一张图。

\n\n

现在您的(本地)Git 存储库已以正确的方式设置,您必须告诉 GitHub 上的 Git 将其后 master退一步。为此,您需要使用git push --forcegit push --force-with-lease

\n\n
git push --force-with-lease origin master\n
Run Code Online (Sandbox Code Playgroud)\n\n

两者之间的区别在于,--force-with-lease他们的 Git(GitHub 上的那个)检查您认为他们拥有的内容master,他们确实拥有master。所以--force-with-lease与更简单的相比增加了一点安全性--force如果其他人也使用这个其他存储库,那么

\n\n

正常情况git push下,您的 Git 会调用另一个 Git\xe2\x80\x94(在本例中为 GitHub\xe2\x80\x94 上的 Git\xe2\x80\x94),并确定您是否有新的提交。如果您确实有新的提交,您的 Git 会将其发送过来。然后,最后,你的 Git 向他们提出一个礼貌的请求:如果可以,请设置你的分支名称 ______ 来提交哈希值 _______。Git 将填写两个空格:名称是您使用的分支名称,即masterin git push origin master,哈希 ID 是名称当前在您的分支中表示的哈希 ID。Git 中表示的哈希 ID。

\n\n

强制推送会做同样的事情,只是最后的请求根本不礼貌:这是一个需求,将你的分支名称 _______ 设置为 ________!

\n\n

git push如果不丢失任何提交,参与 a 的其他 Git将接受礼貌请求。在这种情况下,您的请求或命令专门设计用于使它们丢失commit S。也就是说,他们有:

\n\n
...--o--o--S   <-- master\n         \\\n          o--o--...--o   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

你希望他们赶紧让S开并表明他们的master观点是之前的提交S。所以你需要某种强力的推动。

\n\n

您可以恢复挤压合并,然后重新设置基准

\n\n

git revert命令的工作原理是添加一个新的提交,以撤消先前提交的影响。由于这增加了提交,Git 很乐意接受新的提交。这种特殊的方法在这里有点愚蠢,所以虽然我将绘制效果,但它可能不是您想要的:

\n\n
...--o--H--S--U   <-- master\n         \\\n          o--o--...--o   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

H我在这里给出了一个名称的起点提交develop。原因是commit 中的快照(U undo for S)将与 commit 中的快照匹配H

\n\n

您现在可以用于git rebase整个提交系列复制develop到新的提交系列。这些新提交具有不同的哈希 ID 和不同的父链接,但具有相同的快照与以前

\n\n
                o--o--...--o   <-- develop (HEAD)\n               /\n...--o--H--S--U   <-- master, origin/master\n         \\\n          o--o--...--o   <-- origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

您现在的情况与您意外进行 commit 之前的情况相同S。你必须强制推送你的更新develop送到 GitHub。(然后,当然,您仍然必须合并所有内容!)

\n\n

您可以继续进行真正的合并

\n\n

你可以离开南瓜提交S在适当的位置并进行真正的合并:

\n\n
git fetch                           # if needed\ngit checkout master\ngit merge --ff-only origin/master   # if needed\n
Run Code Online (Sandbox Code Playgroud)\n\n

这样你就有:

\n\n
...--G--H--S   <-- master (HEAD), origin/master\n         \\\n          I--...--R   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

进而:

\n\n
git merge develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

生产:

\n\n
             ,--------<-- origin/master\n            .\n...--G--H--S--------M   <-- master (HEAD)\n         \\         /\n          I--...--R   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

您现在可以git push origin master让他们接受合并提交M,给您:

\n\n
...--G--H--S--------M   <-- master (HEAD), origin/master\n         \\         /\n          I--...--R   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

在您自己的本地 Git 存储库中。

\n\n

您可以删除 S,快进合并您的develop以及强制推送

\n\n

最后,这可能就是您想要个人本地 Git 存储库中看到的内容:

\n\n
          S   <-- origin/master\n         /\n...--G--H--I--...--R   <-- develop, master (HEAD), origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

这也许就是你所拥有的:

\n\n
          S   <-- origin/master\n         /\n...--G--H   <-- master (HEAD)\n         \\\n          I--...--R   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者也许你现在有:

\n\n
          S   <-- master (HEAD). origin/master\n         /\n...--G--H\n         \\\n          I--...--R   <-- develop, origin/develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了得到你想要的,无论,您都可以:

\n\n
git checkout master\ngit reset --hard develop\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于您develop当前指向 commit R,因此会检查 commit R,使您的master点提交R,并留下您想要的图表。然后您可以使用强制推送:

\n\n
git push --force-with-lease origin master\n
Run Code Online (Sandbox Code Playgroud)\n\n

发送您拥有但他们没有的任何提交(没有,他们已经拥有所有这些提交)并命令它们:我认为您master标识了 commit S。如果是这样,请立即使其识别提交R 他们通常会服从这个命令,你就会得到你想要的。

\n