由于您已经推送了两个功能分支,因此您根本不应该进行变基。强烈建议不要对已发布的分支进行变基,因为它会破坏其他开发人员的存储库。原因是 rebase 只是一个完全重写的提交。您正在变基的提交将被重新创建,内容已更改,并且最重要的是具有不同的哈希值。这会导致新提交与旧提交不兼容,因此无论谁拥有旧提交,最终都会与取代它们的新提交发生冲突。
正确的解决方案是简单地合并更改。虽然这可能最终看起来不太漂亮,但这是一种非破坏性操作,不会更改现有的提交。所发生的只是提交是added这不会导致推或拉时出现问题。
话虽这么说,你can变基并仍然发布更改的分支。但要做到这一点,您将需要强制推送分支,而拉动这些更改的其他开发人员将需要将其分支重置为新版本。
将我下面的一些评论合并到答案中:重要的是要理解,在 Git 中,分支只是指向提交的指针。整个历史(没有分支)是一个大型非循环图,其中提交仅指向其父提交。因此,以问题中的示例为例,这是历史记录,没有任何分支指针:
A -- B -- C -- D -- E
\ /
F --- G
\
H -- I -- J
每个字母代表一个提交,并且所有提交都连接到向左转是它的父级。例如F
的父级是A
, and C
是与父母的合并提交B
and G
.
现在,如果我们向该可视化添加分支,那么我们只需添加指向某些提交的指针。它实际上没有别的(分支实际上只是一个包含提交哈希的文件):
master
↓
A -- B -- C -- D -- E
\ /
F --- G ← feature-1
\
H -- I -- J
↑
feature-2
现在,想象一下我们承诺feature-2
分支。我们添加对树的承诺......
\
H -- I -- J -- K
↑
feature-2
…然后我们将分支指针向前移动一位:
\
H -- I -- J -- K
↑
feature-2
现在,为了理解推送期间发生的情况,我们需要记住远程分支也只是分支,因此只是另一组指针。所以它实际上看起来像这样:
\
H -- I -- J ----------- K
↑ ↑
origin/feature-2 feature-2
我想你可以想象现在推送期间会发生什么:我们告诉远程存储库更新其分支指针,以便它指向K
。但服务器只有J
,所以我们需要为服务器提供一切来构造可通过以下方式访问的树K
(因此其间的任何其他提交,以及这些提交的所有实际内容)。但当然我们不需要用身体去推动J
, or H
, 甚至A
(虽然这些都是技术上 on the feature-2
分支,因为你可以reach他们); Git 足够聪明,可以找出哪些对象实际上丢失了(您可以在开始推送时看到 Git 的计算)。
因此,一旦我们将丢失的对象传输到远程存储库,我们就会告诉远程存储库更新其feature-1
指针,所以它也将指向K
。如果成功,我们还会更新我们的远程分支(origin/feature-2
)也指向它(只是为了保持同步)。
现在,合并的情况确实是一样的。想象一下我们合并了master
into feature-2
(using git merge master
开启时feature-2
):
master
↓
A -- B -- C -- D -- E -----
\ / \
F --- G ← feature-1 \
\ \
H -- I -- J -- K -- L
↑
feature-2
现在,如果我们想推动feature-2
,我们再次需要向远程存储库提供它没有的所有对象。由于我们现在正在进行合并提交,因此我们需要检查all家长:所以如果服务器没有K
我们需要推动K
;而且,如果没有E
,我们必须推动E
。当然,我们需要再次跟踪这些父对象,以确保所有对象都存在于遥控器上。一旦完成,我们只需再次告诉远程设备更新分支指针。
总结一下:一个分支contains所有可以通过在非循环树中导航其提交的父级来访问的提交。但即使这意味着分支通常非常“大”(就历史长度而言),Git 也只会将这些对象传输到它没有的远程存储库。因此,尽管合并可以向分支添加更多提交,但如果远程已经从另一个分支知道这些提交,则不一定必须传输这些提交。
最后,关于变基的最后几句话:上面我们做了git merge master
合并master
分支到feature-2
。如果我们这样做了git rebase master
相反,完整的树现在看起来像这样:
master feature-2
↓ ↓
A -- B -- C -- D -- E -- H' -- I' -- J' -- K'
\ /
F --- G ← feature-1
\
H -- I -- J -- K
↑
origin/feature-2
如您所见,有新的提交H'
, I'
, J'
and K'
。这些是重写的提交,以便它们从E
(where master
在变基时指向)而不是G
。由于我们重新调整基础,因此没有合并提交L
。如上所述,原来的提交仍然存在。只是没有剩下的指针指向它们;因此它们会“丢失”并最终被垃圾收集。
那么现在推的时候有什么问题呢?远程分支仍然指向原来的分支K
,但我们希望它指向K'
现在。因此,我们开始为远程存储库提供它所需的所有对象,就像以前一样。但是当我们告诉它设置更新分支指针时,它会拒绝这样做。原因是通过将指针设置为K'
它必须“回到历史”并忽略提交的存在H
to K
。它不知道我们已经重新设置了这些基础,并且重写后的内容和原始内容之间也没有联系。所以为了防止意外的数据丢失,远程会拒绝更新分支指针。
现在你可以力推分支。这将告诉远程存储库更新分支指针,即使这样做会丢弃那些原始提交。所以你这样做,情况会是这样的:
origin/feature-2
master feature-2
↓ ↓
A -- B -- C -- D -- E -- H' -- I' -- J' -- K'
\ /
F --- G ← feature-1
到目前为止,一切都很好:您决定对分支进行变基,并告诉远程存储库接受该分支而无需质疑它。但现在想象我想要pull那;我的分支仍然指向I
。因此,运行 pull 与反向推送的作用相同:遥控器为我提供完成历史记录所需的所有对象,然后它告诉我在哪里设置分支指针。那时,我的本地 Git 拒绝这样做,原因与远程存储库之前这样做的原因相同。
通过之前的推送,我们知道我们想要替换原来的提交;但对于拉动,我们就没有这样的能力,所以我们现在需要调查或四处询问,我们是否应该更换本地分支,或者远程是否确实存在一些故障。如果我们在本地自己做一些工作,而我们现在想要合并,情况会变得更糟。
这些问题发生在每个曾经获取过这些原始提交的人身上。一般来说,您希望完全避免这种混乱,因此规则是永远不要对已经发布的内容进行 rebase。只要没有其他人获得这些原始提交,您就可以进行变基,但是一旦这种情况不再存在,那么对于所有相关人员来说,这将是一团糟。所以合并绝对是首选。