您需要小心交互式变基(或任何变基,但实际上它更容易显示为交互式变基,因为它非常灵活)。
详细考虑提交图可能有所帮助。具体来说,这种类型的绘图本质上有些错误:
(master) A -> B -> C -> D
更好的绘制方法是:
A <-B <-C <-D <--master
关键是将分支名称放在右侧,并带有箭头。 其余的箭头可以转换为直接链接:
A
因为所有提交都是只读的。很难画出内部箭头,所以我们知道它们实际上
不能更改,因此可以将它们绘制为连接线。至少最初绘制它们的原因是箭头与子代相关联,而不是父代:子代知道它们的父提交是谁——例如,
D
知道要返回到
C
,但父代不知道它们的子代是谁。无法从
A
到
B
;我们只能从
B
返回到
A
,然后意识到嘿,我们从
B
到达了这里,因此必须有一条从
A
到
B
的路径。
这是使用Git时的关键认识:
它所有的操作都是反向的。
所有这些事情之所以重要,是因为在并行开发过程中会发生什么。正如您所说,您创建了
X
、
Y
和
Z
:
A--B--C--D <-- master
\
X--Y--Z <-- my-branch
您的
X
知道
D
,但是与您共享的多个Git存储库一样,
D
却不知道您的
X
。
同时,其他人创建了
E
,它也指向
D
。如果我们想象一种某种神奇的超级存储库可以同时知道
E
、
X-Y-Z
,那么我们可以将其绘制如下:
E <
/
A
\
X
然后他们——这里的“他们”指的是GitHub上的开源组织——将第三人称的E
收入了他们自己的master
中:
E <
/
A
(他们可能还不知道你的 X-Y-Z
,所以我们从图纸上将它们去掉)。现在你可以运行 git fetch upstream
命令将 GitHub 仓库版本更新到你的本地计算机,并且在你自己的仓库中,仍然有 X-Y-Z
:
E <
/
A
\
X
(I'm assuming here that your
origin
represents
your GitHub fork, and your
upstream
represents the original GitHub repository from which you made your fork).
不使用交互式变基
当你运行命令
git rebase upstream/master
或让你的
master
指向提交
E
并运行
git rebase master
时,Git会复制提交
X
,
Y
和
Z
:
X'-Y'-Z' <-- my-branch (HEAD)
/
E <-- upstream/master
/
A--B--C--D <-- master, origin/master
\
X--Y--Z <-- origin/my-branch
(这个示意图假设您尚未更新您的master
; 如果您已经更新了,我们可以稍微调整一下示意图)。
这些副本——标有勾号,X'-Y'-Z'
——已准备好供您强制推送到您的分叉中。 假设您此时这样做,您的分叉将拥有它们,原始的X-Y-Z
链将被放弃:
X'-Y'-Z' <-- my-branch (HEAD), origin/my-branch
/
E <-- upstream/master
/
A--B--C--D <-- master, origin/master
\
X--Y--Z [abandoned]
如果我们同时更新您自己的
master
和
origin/master
,我们就可以将所有内容简化为以下形式:
A
\
X'-Y'-Z' <-- my-branch (HEAD), origin/my-branch
这些内容都在你的电脑上的存储库中,如果是在GitHub上,你的分支只需命名为master
和my-branch
,没有origin/
部分,也没有upstream/
部分。
你的拉取请求将自动更新为使用新的X'-Y'-Z'
提交,这些提交仅在提交E
的基础上添加,因此如果他们的存储库仍以提交E
结束,则不应该存在吸收你的提交的问题。
使用交互式变基
如果你想将Y'
压缩到X'
中,并对Z'
的消息进行处理,现在可以使用交互式变基来完成。或者,你可以选择在初始变基的基础上执行所有操作:
$ git checkout my-branch
$ git rebase -i upstream/master
请注意,这里的
upstream/master
实际上指的是我们图表中的“提交
E
”,它是其中一个将
E
作为
upstream/master
,而将
Z
或
Z'
作为你自己的
my-branch
提交顶部的提交之一。
Git 现在将列出那些位于
my-branch
上但不在
upstream/master
上的三个提交,即从
Z
或
Z'
开始并向后工作到
A
,列出这些提交,然后从该列表中删除所有从
E
开始向后工作的提交。这三个提交的哈希 ID 将进入三个
pick
命令。
现在,您可以像以前一样将一个更改为 squash,将另一个更改为编辑:由于您仅处理自己的提交,而不处理其他人的提交,因此您不会将任何其他人的提交复制到新的和不同的提交中。编辑完成后,您将再次拥有新的提交——如果您已经将
X
复制到
X'
,将
Y
复制到
Y'
,并将
Z
复制到
Z'
,现在您将拥有
X"
,它是
X'
+
Y'
(squashed) 和
Z"
是
Z'
的编辑版本,但连接到
X"
。
X"-Z" <-- my-branch (HEAD)
/
A--B--C--D--E <-- master, origin/master, upstream/master
\
X'-Y'-Z' <-- origin/my-branch
现在,您可以强制推送这些到您的分支,使得它们的my-branch
(在这里称为origin/my-branch
)指向提交Z"
,这样您的分支就被更新了,您的pull请求也会自动更新,并且您准备好让他们查看X"
和Z"
了。
E
的提交复制到了一个新的E'
。然后,您的拉取请求具有您的E'
在D
之上,这与他们的E
在D
之上发生冲突(或者在C
之上拥有两个副本)。很难没有看到实际的提交图,而GitHub坚持隐藏实际的图(他们的“网络图”完全是另一回事,并且似乎永远都不太正确)。 - torek--no-ff
。 - torek