在父级被压紧并提交后,如何根据master重新设置分支?

时间:2019-06-28 09:48:56

标签: git git-merge squash git-squash

我在Gitlab中使用了一个乐观的工作流程,该流程假定我的大多数合并请求将被接受而无需更改。流程如下所示:

  1. 为分支cool-feature-A提交合并请求
  2. 基于cool-feature-A创建一个名为cool-feature-B的新分支。开始在此分支上进行开发。
  3. 一位同事批准了我对cool-feature-A的合并请求。
  4. 我将cool-feature-B换成master(这很轻松),并继续发展。

如果同事在步骤3进行壁球合并,则会出现问题。这将重写cool-feature-B中存在的大量历史记录。当我进入第4步时,我面临着一个合并之痛的世界。

如何避免这种情况发生?

1 个答案:

答案 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}},BC,并设置 Dcool-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 ,然后依次是DD等等的可到达的提交列表,然后减去提交列表可从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:

  1. 枚举cool-feature-B可到达的提交:DDCB,...;
  2. 枚举A可到达的提交:XX,...;
  3. 从步骤1的集合中减去步骤2的集合,剩下ADC
  4. 复制这些提交(以未向后排序的顺序),以便它们在B之后出现。

请注意,第2步和第3步都使用单词X来查找提交:对于第2步,要复制的提交是从{{1 }}。对于步骤3,放置副本的位置是在master的提示之后

但是如果您运行:

master

您在第2步和第3步中让Git使用不同项。

  • 从步骤2开始,要复制的提交列表来自master:从git rebase --onto master cool-feature-A 中减去cool-feature-A..cool-feature-B。这样就只需提交C-B-A-...
  • 第3步中放置副本的位置来自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以使它已经存在”,将很聪明地根本不费心去复制,而只是将所有内容保留在原处。