我在Gitlab中使用了一个乐观的工作流程,该流程假定我的大多数合并请求将被接受而无需更改。流程如下所示:
cool-feature-A
提交合并请求cool-feature-A
创建一个名为cool-feature-B
的新分支。开始在此分支上进行开发。cool-feature-A
的合并请求。cool-feature-B
换成master
(这很轻松),并继续发展。如果同事在步骤3进行壁球合并,则会出现问题。这将重写cool-feature-B
中存在的大量历史记录。当我进入第4步时,我面临着一个合并之痛的世界。
如何避免这种情况发生?
答案 0 :(得分:3)
本质上,您必须告诉Git:我想将cool-feature-B
换成master
的基础,但是我想复制一组与提交不同的提交您通常会在这里进行计算。最简单的方法是使用--onto
。也就是说,通常您会按照您所说的运行:
git checkout cool-feature-B
git rebase master
但是您需要这样做:
git rebase --onto master cool-feature-A
之前,您将删除自己的分支名称/标签cool-feature-A
。
即使他们使用普通合并,您也可以始终这样做。这样做没有什么害处,除了您必须输入更多内容并记住(无论您喜欢什么)以后需要这个--onto
,它需要正确的名称或哈希ID。
(如果您可以将cool-feature-A
所指向的提交的哈希ID放入cool-feature-B
上游的引用日志中,则可以使用--fork-point
功能如果有问题的reflog条目没有过期,Git会在以后自动为您计算出来。但是,总的来说,这比手动完成要困难得多。此外,它还有整个“提供的”部分。)
让我们像往常一样开始绘制图形。最初,您可以在自己的存储库中进行此设置:
...--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
处,他们获得了提交{ 1}},B
和C
,并设置其 D
和cool-feature-A
的名称来记住提交cool-feature-B
和{{ 1}},因此您自己的Git添加了这两个名称的您 C
变体。
如果他们(无论他们是谁,即在D
上控制存储库的人)进行了快速合并,则他们会将其母版向上滑动以指向提交origin/
。 (请注意, GitHub Web界面没有用于进行快速合并的按钮。我没有使用过GitLab的Web界面;它在各种方面可能有所不同。)他们强制进行真正的合并,这是GitHub网页上“立即合并” clicky按钮的默认设置;再次,我不知道GitLab在这里做什么–他们将进行新的合并提交origin
:
C
(这里我故意剥离了 all 的名称,因为它们的名称与您的名称不太匹配)。他们可能会删除(甚至可能实际上从未创建过)他们的E
名称。无论哪种方式,您都可以在更新...--A------E
\ /
B--C
\
D
名称的同时拥有自己的Git快进 cool-feature-A
名称:
master
或:
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
Git现在将枚举cool-feature-A
-git checkout cool-feature-B
git rebase master
,然后依次是D
和D
等等的可到达的提交列表,然后减去提交列表可从C
到达:B
(如果存在),然后master
(如果存在E
)和A
(是否存在E
),然后是C
,依此类推。减法的结果就是提交E
。
您的Git现在复制了该列表中的提交,因此新副本位于B
的尖端之后:即,如果D
是真正的合并,则位于master
之后,或者E
之后如果他们进行了快速合并。因此,Git要么将C
复制到D
之后的新提交D'
:
E
否则它就 D' <-- cool-feature-B
/
...--A------E <-- master, origin/master
\ /
B--C
\
D <-- origin/cool-feature-B
离开了,因为它已经在D
之后了(所以没有什么新内容可绘制了。)
当它们使用与GitHub的“ squash and merge”或“ rebase and merge” clicky按钮等效的任何GitLab时,就会出现棘手的部分。我将跳过“ rebase and merge”的情况(通常不会造成任何问题,因为Git的rebase也会检查补丁ID),然后直接处理最困难的情况,即“ squash and merge”。如您正确指出的那样,这将提交一个新的和不同的单次提交。当您将新提交提交到自己的存储库中时(例如,在C
之后),您将拥有:
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
),但其提交(单)父是现有提交E
。因此,从A
开始的历史记录(通过反向工作枚举的提交列表)只是X
,然后是X
,然后是A
之前的所有提交。
如果您在A
上运行常规git rebase master
,则Git:
cool-feature-B
可到达的提交:D
,D
,C
,B
,...; A
可到达的提交:X
,X
,...; A
,D
,C
; B
之后出现。请注意,第2步和第3步都使用单词X
来查找提交:对于第2步,要复制的提交是从{{1 }}。对于步骤3,放置副本的位置是在master
的提示之后 。
但是如果您运行:
master
您在第2步和第3步中让Git使用不同项。
master
:从git rebase --onto master cool-feature-A
中减去cool-feature-A..cool-feature-B
。这样就只需提交C-B-A-...
。D-C-B-A-...
:将它们放置在D
之后。因此,现在Git仅将--onto master
复制到X
,然后D
将名称D'
移到指向git rebase
的地方:
cool-feature-B
这就是您想要的。
如果他们(控制GitLab仓库的人)使用了真正的合并或快速转发而不是完全合并,那么所有这些仍然有效:您可以让Git枚举{{ 1}}-on-backwards,但将D'
-on-backwards从列表中删除,仅留下 D' <-- cool-feature-B
/
...--A--X <-- master, origin/master
\
B--C <-- cool-feature-A [, origin/cool-feature-A]
\
D <-- origin/cool-feature-B
进行复制;然后Git会复制D
,以便它出现在C
(真正的合并情况)或D
(快进情况)之后。快进的情况是,“复制D以使它已经存在”,将很聪明地根本不费心去复制,而只是将所有内容保留在原处。