- 提交一个名为
cool-feature-A
的分支的合并请求 - 基于
cool-feature-A
创建一个新分支,名为cool-feature-B
,并开始在此分支上进行开发。 - 同事批准了我对
cool-feature-A
的合并请求。 - 我将
cool-feature-B
与master
进行变基(这很容易),然后继续开发。
cool-feature-B
中的大量历史记录。当我到达第4步时,我将遇到许多合并痛点。如何避免这种情况的发生?
cool-feature-A
的分支的合并请求cool-feature-A
创建一个新分支,名为cool-feature-B
,并开始在此分支上进行开发。cool-feature-A
的合并请求。cool-feature-B
与master
进行变基(这很容易),然后继续开发。cool-feature-B
中的大量历史记录。当我到达第4步时,我将遇到许多合并痛点。本质上,你需要告诉 Git:我想要将cool-feature-B
在master
的基础上进行变基,但我想要复制一组不同于通常计算的提交。最简单的方法是使用--onto
。也就是说,通常你会运行,就像你说的那样:
git checkout cool-feature-B
git rebase master
但你需要做:
git rebase --onto master cool-feature-A
在你删除你自己的分支名称/标签 cool-feature-A
之前。
即使他们使用了普通合并,你仍然可以这样做。 这不会有什么影响,除了你需要输入更多内容并记住(无论你喜欢什么方式)以后需要这个正确的名称或哈希 ID 的--onto
。
(如果您可以将此刻指向 cool-feature-A
的提交的哈希 ID 放入 cool-feature-B
的上游的 reflog 中,您可以使用 --fork-point
功能让 Git 自动为您计算,前提是所涉及的 reflog 条目没有过期。但总的来说,这可能比手动执行要难一些。另外还有那个“提供”的部分。)
像往常一样,让我们从图形绘制开始。最初,在您自己的存储库中,您有以下设置:
...--A <-- master, origin/master
\
B--C <-- cool-feature-A
\
D <-- cool-feature-B
一旦你运行了git push origin cool-feature-A cool-feature-B
,你就有:
...--A <-- master, origin/master
\
B--C <-- cool-feature-A, origin/cool-feature-A
\
D <-- cool-feature-B, origin/cool-feature-B
注意,我们所做的一切只是添加了两个origin/
名称(即两个远程跟踪名称):在他们的存储库中,在origin
处,他们获取了提交B
、C
和D
,并将其cool-feature-A
和cool-feature-B
名称设置为分别记住提交C
和D
,因此你自己的Git添加了这两个名称的origin/
变体。如果他们(任何“他们”的人-控制origin
上存储库的人)执行快进合并,他们将把他们的主干向上移动以指向提交C
。(请注意,GitHub web界面没有按钮可以进行快进合并。我没有使用过GitLab的Web界面;它可能在各种方面有所不同。)如果他们强制执行实际合并-这就是GitHub网页上的“现在合并”按钮默认执行的操作;再次说明,我不知道GitLab在这里做什么-他们将创建一个新的合并提交E
:
...--A------E
\ /
B--C
\
D
(这里我故意去掉了所有名称,因为它们与您的名称不完全匹配)。他们可能会删除(或甚至从未实际创建)他们的cool-feature-A
名称。无论哪种方式,您都可以快速转发您自己的Git 主名称,同时更新您的origin/*
名称:
...--A------E <-- master, origin/master
\ /
B--C <-- cool-feature-A [, origin/cool-feature-A if it exists]
\
D <-- cool-feature-B, origin/cool-feature-B
或者:
...--A
\
B--C <-- cool-feature-A, master, origin/master [, origin/cool-feature-A]
\
D <-- cool-feature-B, origin/cool-feature-B
无论您是否现在删除名称cool-feature-A
,为了以后的方便绘图,假设您这样做,如果运行:
git checkout cool-feature-B
git rebase master
Git将枚举从D
-D
,然后是C
,再然后是B
等可达提交的列表,并减去从master
可达提交的列表:E
(如果存在) ,然后A
(如果存在E
)和C
(无论E
是否存在),然后是B
等。减法的结果就是提交D
。
现在,你的Git会复制此列表中的提交,使得新副本出现在master
提示后面:即在实际合并之后的E
后面,或者在快进合并之后的C
后面。因此,Git将D
复制到一个新的提交D'
,并将其放置在E
之后。
D' <-- cool-feature-B
/
...--A------E <-- master, origin/master
\ /
B--C
\
D <-- origin/cool-feature-B
或者它保留了C
,并且不做任何操作,因为D
已经在C
之后(所以没有新的内容需要绘制)。
棘手的部分出现在他们使用GitLab相当于GitHub的"squash and merge"或"rebase and merge"按钮时。我将跳过"rebase and merge"的情况(通常不会有问题,因为Git的rebase也会检查patch-ID),直接进入困难情况——"squash and merge"。如你所说,这将生成一个新而不同的单一提交。当你将该新提交带入自己的存储库中——例如,在执行git fetch
之后——你会得到:
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- cool-feature-B, origin/cool-feature-B
X
是一个新提交的结果,其快照将与合并E
匹配(如果他们进行合并),但其父提交是现有的提交A
。因此,历史记录——从X
向后工作列举的提交列表只有X
,然后是A
,再然后是A
之前的任何提交。
如果在cool-feature-B
上运行常规的git rebase master
,Git将:
D
可达的提交: D
,C
,B
,A
,...;X
可达的提交: X
,A
,...;D
,C
,B
;X
之后。请注意,步骤2和3都使用单词master
来查找提交:要复制的提交(步骤2)是那些从master
无法到达的;将复制品放置的位置(步骤3)是在master
末端之后。
但如果您运行:
git rebase --onto master cool-feature-A
你在步骤2和3中使用了不同的Git项目:
cool-feature-A..cool-feature-B
:从D-C-B-A-...
中减去C-B-A-...
。这将仅剩下提交D
。--onto master
:把它们放在X
之后。因此,现在Git仅复制D
到D'
,然后git rebase
拉动名称cool-feature-B
指向D'
:
D' <-- cool-feature-B
/
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- origin/cool-feature-B
如果控制GitLab repo的人使用了真正的合并或快进却不完全是合并,这一切仍将起作用:您的Git将在向后枚举D时从列表中删除C-on-backwards,只留下D进行复制;然后Git会复制D以使其出现在E之后(真正的合并情况)或C(快速前进情况)之后。快速前进情况,“将D复制到它已经存在的位置”,聪明地根本不需要复制,而只需保持一切不变。git push -f
安全吗? - zeroliuorigin/cool-feature-B
是你的 Git 记录了另一个 Git 的 cool-feature-B
名称。因此,你必须说服那个 Git 改变它自己的名称——更准确地说,改变存储在其分支名称中的哈希 ID——为此,通常需要某种形式的强制推送。--force-with-lease
选项是在你担心其他人也可能尝试更新其他 Git 的分支名称时执行此操作的好方法;如果你确定现在没有其他人在处理它,git push -f
更简单。 - torek
git checkout cool-feature-B && git rebase --fork-point master
吗?当我在rebase多个依赖分支(例如cool-feature-A
、cool-feature-B
、cool-feature-C
)时,使用--fork-point
效果非常好,尽管我承认我没有尝试过压缩合并。 - Chris--fork-point
使用分支上游的 reflog,因此为了使其工作,您必须在上游拥有提交B
和C
(并且已经将它们看到进入您自己的存储库),但这在一般情况下都不会成立。 - torek